-
-
Save HumanEquivalentUnit/4bc5a99b6bedd8987e90d3388fbbd600 to your computer and use it in GitHub Desktop.
Invoke a scriptblock in powershell with impersonation
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
# Copyright: (c) 2020, Jordan Borean (@jborean93) <jborean93@gmail.com> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
Add-Type -Namespace PInvoke -Name NativeMethods -MemberDefinition @' | |
[DllImport("Kernel32.dll")] | |
public static extern bool CloseHandle( | |
IntPtr hObject); | |
[DllImport("Advapi32.dll", SetLastError = true)] | |
public static extern bool ImpersonateLoggedOnUser( | |
IntPtr hToken); | |
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
public static extern bool LogonUserW( | |
string lpszUsername, | |
string lpszDomain, | |
IntPtr lpszPassword, | |
UInt32 dwLogonType, | |
UInt32 dwLogonProvider, | |
out IntPtr phToken); | |
[DllImport("Advapi32.dll")] | |
public static extern bool RevertToSelf(); | |
'@ | |
Function Invoke-WithImpersonation { | |
<# | |
.SYNOPSIS | |
Invoke a scriptblock as another user. | |
.DESCRIPTION | |
Invoke a scriptblock and run it in the context of another user as supplied by -Credential. | |
.PARAMETER ScriptBlock | |
The PowerShell code to run. It is recommended to use '{}.GetNewClosure()' to ensure the scriptblock has access to | |
the same values where it was defined. Anything output by this scriptblock will also be outputted by | |
Invoke-WithImpersonation. | |
.PARAMETER Credential | |
The PSCredential that specifies the user to run the scriptblock as. This needs to be a valid local or domain user | |
except when using '-LogonType NewCredential'. The user specified must have been granted the 'logon as ...' right | |
for the -LogonType that was requested (except for -LogonType NewCredential). | |
.PARAMETER LogonType | |
The logon type to use for the impersonated token. By default it is set to 'Interactive' which is the logon type | |
used when a user has logged on interactively. Each logon type has their own unique characteristics as specified. | |
Batch: Replicates running as a scheduled task, will typically have the full rights of the user specified. | |
Interactive: Replicates running as a normal logged on user, may have limited rights depending on whether UAC | |
is enabled. | |
Network: Replicates running from a network logon like WinRM, will not be able to delegate it's credential to | |
further downstream servers. | |
NetworkCleartext: Like Network but will have access to its credentials for delegation, similar to using | |
CredSSP auth for WinRM. | |
NewCredential: Can be used to specify any credentials and any network auth attempts will use those credentials. | |
Any local actions are run as the existing users token. | |
Service: Replicates running as a Windows service. | |
.EXAMPLE Run as an interactive logon | |
$cred = Get-Credential | |
Invoke-WithImpersonation -Credential $cred -ScriptBlock { | |
[System.Security.Principal.WindowsIdentity]::GetCurrent().Name | |
}.GetNewClosure() | |
.EXAMPLE Access a network path with explicit credentials | |
$cred = Get-Credential # Can be any username/password, does not have to be a valid local or domain account. | |
$files = Invoke-WithImpersonation -Credential $cred -LogonType NewCredential -ScriptBlock { | |
Get-ChildItem -Path \\192.168.1.1\share\folder | |
}.GetNewClosure() | |
.NOTES | |
Starting a new process in the scriptblock will run as the original user and not the user supplied by -Credential. | |
Use 'Start-Process' with -Credential to create a new process as another user. | |
#> | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory=$true)] | |
[ScriptBlock] | |
$ScriptBlock, | |
[Parameter(Mandatory=$true)] | |
[PSCredential] | |
$Credential, | |
[String] | |
[ValidateSet('Batch', 'Interactive', 'Network', 'NetworkCleartext', 'NewCredential', 'Service')] | |
$LogonType = 'Interactive' | |
) | |
$logonTypeInt = switch($LogonType) { | |
Interactive { 2 } # LOGON32_LOGON_INTERACTIVE | |
Network { 3 } # LOGON32_LOGON_NETWORK | |
Batch { 4 } # LOGON32_LOGON_BATCH | |
Service { 5 } # LOGON32_LOGON_SERVICE | |
NetworkCleartext { 8 } # LOGON32_LOGON_NETWORK_CLEARTEXT | |
NewCredential { 9 } # LOGON32_LOGON_NEW_CREDENTIALS | |
} | |
$user = $Credential.UserName | |
$domain = $null | |
if ($user.Contains('\')) { | |
$domain, $user = $user -split '\\', 2 | |
} | |
$passPtr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Credential.Password) | |
try { | |
$token = [IntPtr]::Zero | |
$res = [PInvoke.NativeMethods]::LogonUserW( | |
$user, | |
$domain, | |
$passPtr, | |
$logonTypeInt, | |
0, # LOGON32_PROVIDER_DEFAULT | |
[ref]$token | |
); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
} finally { | |
[System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($passPtr) | |
} | |
if (-not $res) { | |
$exp = [System.ComponentModel.Win32Exception]$err | |
Write-Error -Message "Failed to log on user: $($exp.Exception.Message)" -Exception $exp | |
return | |
} | |
try { | |
$res = [PInvoke.NativeMethods]::ImpersonateLoggedOnUser( | |
$token | |
); $err = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error() | |
if (-not $res) { | |
$exp = [System.ComponentModel.Win32Exception]$err | |
Write-Error -Message "Failed to impersonate user: $($exp.Exception.Message)" -Exception $exp | |
return | |
} | |
try { | |
&$ScriptBlock | |
} finally { | |
$null = [PInvoke.NativeMethods]::RevertToSelf() | |
} | |
} finally { | |
$null = [PInvoke.NativeMethods]::CloseHandle($token) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment