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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
.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”