Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save HumanEquivalentUnit/4bc5a99b6bedd8987e90d3388fbbd600 to your computer and use it in GitHub Desktop.
Save HumanEquivalentUnit/4bc5a99b6bedd8987e90d3388fbbd600 to your computer and use it in GitHub Desktop.
Invoke a scriptblock in powershell with impersonation
# 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