I was asked to see if I could create a script to create a Users profile without the user being logged in.
So I searched Bing’d and Goog’d and couldn’t find a PowerShell Module where you could do that. I then began the process of searching for folks that could get me started. Once I got the “starting” information I was able to put a script together. this led me to a base script to create a user profile with Pinvoke . when I first put this code together it caused ISE / Powershell to Crash. So then I was again perplexed as to what do I do now. So I Posted a question on it and thankfully someone else had started working on the same thing @MS_dministrator. He gave me a working way to get around the crashes I was experiencing with his script . Now on to what and how it works.
The main task was to create a profile so I’ll explain that first.
In order to use the interopservices / pinvoke I had to bring in System.runtime.interopservices.
thankfully Adamdriscoll did all the heaving lifting with his scripting that creates this type:
Add-Type -TypeDefinition ' using System; using System.Runtime.InteropServices; public static class PInvoke { [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int CreateProfile( [MarshalAs(UnmanagedType.LPWStr)] String pszUserSid, [MarshalAs(UnmanagedType.LPWStr)] String pszUserName, [Out][MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszProfilePath, uint cchProfilePath); } '
The next step was how to call that added type with the proper information.
$pszProfilePath = new-object -typename System.Text.StringBuilder [int]$results = [PInvoke]::CreateProfile($UserSid, $UserName, $pszProfilePath, $ProfilePath) } $stringbuff = new-object system.text.stringbuilder(260) [system.uint32]$a =$stringbuff.capacity $sid = ((get-aduser -id 'brtestlocaluser').sid.value) CreateProfile -usersid $sid -username 'brtestlocaluser' -ProfilePath $a
Here is where I found this code caused my ise and powershell process to crash.
function CreateProfile { param([String]$UserSid, [String]$UserName, [system.uint32]$ProfilePath) Add-Type -TypeDefinition ' using System; using System.Runtime.InteropServices; public static class PInvoke { [DllImport("userenv.dll", SetLastError = true, CharSet = CharSet.Auto)] public static extern int CreateProfile( [MarshalAs(UnmanagedType.LPWStr)] String pszUserSid, [MarshalAs(UnmanagedType.LPWStr)] String pszUserName, [Out][MarshalAs(UnmanagedType.LPWStr)] System.Text.StringBuilder pszProfilePath, uint cchProfilePath); } ' $pszProfilePath = new-object -typename System.Text.StringBuilder [int]$results = [PInvoke]::CreateProfile($UserSid, $UserName, $pszProfilePath, $ProfilePath) } $stringbuff = new-object system.text.stringbuilder(260) [system.uint32]$a =$stringbuff.capacity $sid = ((get-aduser -id 'brtestlocaluser').sid.value) CreateProfile -usersid $sid -username 'brtestlocaluser' -ProfilePath $a
So with that in mind I sent out some Tweets to find out why this was crashing and I came across @MS_dministrator. He had already done some of the work to allow for a Pinvoke to be called. What he did differently than what I was doing was to “wrap” the Pinvoke in a Script Scope. So the code I’m showing above ended up in being two functions one to register the native method the other function to add the native method.
function Register-NativeMethod { [CmdletBinding()] [Alias()] [OutputType([int])] Param ( # Param1 help description [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string]$dll, # Param2 help description [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)] [string] $methodSignature ) $script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; } }
Adding the Native Method:
function Add-NativeMethods { [CmdletBinding()] [Alias()] [OutputType([int])] Param($typeName = 'NativeMethods') $nativeMethodsCode = $script:nativeMethods | ForEach-Object { " [DllImport(`"$($_.Dll)`")] public static extern $($_.Signature); " } Add-Type @" using System; using System.Text; using System.Runtime.InteropServices; public static class $typeName { $nativeMethodsCode } "@ }
Now to show how they are called in the new function that creates a user profile. The first thing that is done is we try and see if the user that we need to create a profile for is Local to the machine.
New-LocalUser -username $UserName -password $Password;
If that user is local then we goto the new-localuser function in the same script. Once that completes we are on to the Pinvoke code. First we declare a name for our method to be from the Pinvoke. In this case it’s going to be USERENVCP. Then we see if it is already declared with the If statement:
$methodName = 'UserEnvCP' $script:nativeMethods = @(); if (-not ([System.Management.Automation.PSTypeName]$MethodName).Type) {
If it’s not in our session then here is where we are going to use the functions described above to get our Pinvoke registered in our session. So now we call Register-NativeMethod with our Dll and the method signature to register it. Then immediately after that we add the native method so we can call it.
Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` [MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` [Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; Add-NativeMethods -typeName $MethodName;
With the $methodname added now we can call it and create our profile:
try { [UserEnvCP]::CreateProfile($userSID.Value, $Username, $sb, $pathLen) | Out-Null; }
Full code for the explained function is below:
function Create-NewProfile { [CmdletBinding()] [Alias()] [OutputType([int])] Param ( # Param1 help description [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] [string]$UserName, # Param2 help description [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=1)] [string] $Password ) Write-Verbose "Creating local user $Username"; try { New-LocalUser -username $UserName -password $Password; } catch { Write-Error $_.Exception.Message; break; } $methodName = 'UserEnvCP' $script:nativeMethods = @(); if (-not ([System.Management.Automation.PSTypeName]$MethodName).Type) { Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` [MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` [Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; Add-NativeMethods -typeName $MethodName; } $localUser = New-Object System.Security.Principal.NTAccount("$UserName"); $userSID = $localUser.Translate([System.Security.Principal.SecurityIdentifier]); $sb = new-object System.Text.StringBuilder(260); $pathLen = $sb.Capacity; Write-Verbose "Creating user profile for $Username"; try { [UserEnvCP]::CreateProfile($userSID.Value, $Username, $sb, $pathLen) | Out-Null; } catch { Write-Error $_.Exception.Message; break; } }
Many thanks to the members of the community that helped me with getting this script built and working (@Ms_dminstrator, @adamdriscoll )…. The entire script can be found on my gist here:
<# | |
.Synopsis | |
Rough PS functions to create new user profiles | |
.DESCRIPTION | |
Call the Create-NewProfile function directly to create a new profile | |
.EXAMPLE | |
Create-NewProfile -Username 'testUser1' -Password 'testUser1' | |
.NOTES | |
Created by: Josh Rickard (@MS_dministrator) and Thom Schumacher (@driberif) | |
Date: 24MAR2017 | |
Location: https://gist.github.com/crshnbrn66/7e81bf20408c05ddb2b4fdf4498477d8 | |
Contact: https://github.com/MSAdministrator | |
MSAdministrator.com | |
https://github.com/crshnbrn66 | |
powershellposse.com | |
#> | |
#Function to create the new local user first | |
function New-LocalUser | |
{ | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
$userName, | |
# Param2 help description | |
[string] | |
$password | |
) | |
$system = [ADSI]"WinNT://$env:COMPUTERNAME"; | |
$user = $system.Create("user",$userName); | |
$user.SetPassword($password); | |
$user.SetInfo(); | |
$flag=$user.UserFlags.value -bor 0x10000; | |
$user.put("userflags",$flag); | |
$user.SetInfo(); | |
$group = [ADSI]("WinNT://$env:COMPUTERNAME/Users"); | |
$group.PSBase.Invoke("Add", $user.PSBase.Path); | |
} | |
#function to register a native method | |
function Register-NativeMethod | |
{ | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
[string]$dll, | |
# Param2 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=1)] | |
[string] | |
$methodSignature | |
) | |
$script:nativeMethods += [PSCustomObject]@{ Dll = $dll; Signature = $methodSignature; } | |
} | |
function Get-Win32LastError | |
{ | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param($typeName = 'LastError') | |
if (-not ([System.Management.Automation.PSTypeName]$typeName).Type) | |
{ | |
$lasterrorCode = $script:lasterror | ForEach-Object{ | |
'[DllImport("kernel32.dll", SetLastError = true)] | |
public static extern uint GetLastError();' | |
} | |
Add-Type @" | |
using System; | |
using System.Text; | |
using System.Runtime.InteropServices; | |
public static class $typeName { | |
$lasterrorCode | |
} | |
"@ | |
} | |
} | |
#function to add native method | |
function Add-NativeMethods | |
{ | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param($typeName = 'NativeMethods') | |
$nativeMethodsCode = $script:nativeMethods | ForEach-Object { " | |
[DllImport(`"$($_.Dll)`")] | |
public static extern $($_.Signature); | |
" } | |
Add-Type @" | |
using System; | |
using System.Text; | |
using System.Runtime.InteropServices; | |
public static class $typeName { | |
$nativeMethodsCode | |
} | |
"@ | |
} | |
#Main function to create the new user profile | |
function Create-NewProfile { | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
[string]$UserName, | |
# Param2 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=1)] | |
[string] | |
$Password | |
) | |
Write-Verbose "Creating local user $Username"; | |
try | |
{ | |
New-LocalUser –username $UserName –password $Password; | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message; | |
break; | |
} | |
$methodName = 'UserEnvCP' | |
$script:nativeMethods = @(); | |
if (-not ([System.Management.Automation.PSTypeName]$MethodName).Type) | |
{ | |
Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` | |
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` | |
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; | |
Add-NativeMethods –typeName $MethodName; | |
} | |
$localUser = New-Object System.Security.Principal.NTAccount("$UserName"); | |
$userSID = $localUser.Translate([System.Security.Principal.SecurityIdentifier]); | |
$sb = new-object System.Text.StringBuilder(260); | |
$pathLen = $sb.Capacity; | |
Write-Verbose "Creating user profile for $Username"; | |
try | |
{ | |
[UserEnvCP]::CreateProfile($userSID.Value, $Username, $sb, $pathLen) | Out-Null; | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message; | |
break; | |
} | |
} | |
function New-ProfileFromSID { | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
[string]$UserName, | |
[string]$domain = 'PHCORP' | |
) | |
$methodname = 'UserEnvCP2' | |
$script:nativeMethods = @(); | |
if (-not ([System.Management.Automation.PSTypeName]$methodname).Type) | |
{ | |
Register-NativeMethod "userenv.dll" "int CreateProfile([MarshalAs(UnmanagedType.LPWStr)] string pszUserSid,` | |
[MarshalAs(UnmanagedType.LPWStr)] string pszUserName,` | |
[Out][MarshalAs(UnmanagedType.LPWStr)] StringBuilder pszProfilePath, uint cchProfilePath)"; | |
Add-NativeMethods –typeName $methodname; | |
} | |
$sb = new-object System.Text.StringBuilder(260); | |
$pathLen = $sb.Capacity; | |
Write-Verbose "Creating user profile for $Username"; | |
#$SID= ((get-aduser -id $UserName -ErrorAction Stop).sid.value) | |
if($domain) | |
{ | |
$objUser = New-Object System.Security.Principal.NTAccount($domain, $UserName) | |
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) | |
$SID = $strSID.Value | |
} | |
else | |
{ | |
$objUser = New-Object System.Security.Principal.NTAccount($UserName) | |
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) | |
$SID = $strSID.Value | |
} | |
Write-Verbose "$UserName SID: $SID" | |
try | |
{ | |
$result = [UserEnvCP2]::CreateProfile($SID, $Username, $sb, $pathLen) | |
if($result -eq '-2147024713') | |
{ | |
$status = "$userName already exists" | |
write-verbose "$username Creation Result: $result" | |
} | |
elseif($result -eq '-2147024809') | |
{ | |
$staus = "$username Not Found" | |
write-verbose "$username creation result: $result" | |
} | |
elseif($result -eq 0) | |
{ | |
$status = "$username Profile has been created" | |
write-verbose "$username Creation Result: $result" | |
} | |
else | |
{ | |
$status = "$UserName unknown return result: $result" | |
} | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message; | |
break; | |
} | |
$status | |
} | |
Function Remove-Profile { | |
[CmdletBinding()] | |
[Alias()] | |
[OutputType([int])] | |
Param | |
( | |
# Param1 help description | |
[Parameter(Mandatory=$true, | |
ValueFromPipelineByPropertyName=$true, | |
Position=0)] | |
[string]$UserName, | |
[string]$ProfilePath, | |
[string]$domain = 'PHCORP' | |
) | |
$methodname = 'userenvDP' | |
$script:nativeMethods = @(); | |
if (-not ([System.Management.Automation.PSTypeName]"$methodname.profile").Type) | |
{ | |
add-type @" | |
using System.Runtime.InteropServices; | |
namespace $typename | |
{ | |
public static class UserEnv | |
{ | |
[DllImport("userenv.dll", CharSet = CharSet.Unicode, ExactSpelling = false, SetLastError = true)] | |
public static extern bool DeleteProfile(string sidString, string profilePath, string computerName); | |
[DllImport("kernel32.dll")] | |
public static extern uint GetLastError(); | |
} | |
public static class Profile | |
{ | |
public static uint Delete(string sidString) | |
{ //Profile path and computer name are optional | |
if (!UserEnv.DeleteProfile(sidString, null, null)) | |
{ | |
return UserEnv.GetLastError(); | |
} | |
return 0; | |
} | |
} | |
} | |
"@ | |
} | |
#$SID= ((get-aduser -id $UserName -ErrorAction Stop).sid.value) | |
if($domain) | |
{ | |
$objUser = New-Object System.Security.Principal.NTAccount($domain, $UserName) | |
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) | |
$SID = $strSID.Value | |
} | |
else | |
{ | |
$objUser = New-Object System.Security.Principal.NTAccount($UserName) | |
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier]) | |
$SID = $strSID.Value | |
} | |
Write-Verbose "$UserName SID: $SID" | |
try | |
{ | |
#http://stackoverflow.com/questions/31949002/c-sharp-delete-user-profile | |
$result = [userenvDP.Profile]::Delete($SID) | |
} | |
catch | |
{ | |
Write-Error $_.Exception.Message; | |
break; | |
} | |
$LastError | |
} |
I hope this helps someone
Until then keep Scripting
Thom
One thought on “Profile creation with PowerShell and the community”