So I have automation that logs into a Windows Server 2019 machine as one user, but then needs to run a command (Invoke-AdminCommand is application specific, not a built-in Windows cmdlet) as an admin user (and I do not want to add the logged in user as an Admin). I've followed answers from here (if you think this is a duplicate question) and none have worked. In the script I do a "whoami" to be sure the session is the correct user, and it is. But the command returns an application specific error stating the user does not have the correct permissions. If I RDP into the same machine as the admin user and run the same command through a Powershell CLI - it works fine.
$username = "domain\adminUser"
$password = "**********" | ConvertTo-SecureString -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList $username,$password
$s = New-PSSession -credential $cred
$sc = {
whoami
Invoke-AdminCommand -Register -Verbose
}
Invoke-Command -Session $s -Scriptblock $sc
Remove-PSSession $s
You may be hitting the double-hop problem. You are remoting to another server to run another command which itself requires authentication. If you can't lean on CredSSP (security risk) or proper account delegation (potentially high overhead in effort to maintain delegations at volume but this is the correct way to go about it).
Note: Basic auth will also work around this issue but I highly highly highly do not recommend using basic auth without at least setting up WinRM over SSL and removing non-HTTPS WinRM listeners.
Whether you are using Kerberos (without proper delegation or CredSSP) or NTLM (at all as NTLM cannot forward tokens) as the authentication scheme you can work around this by passing the credential information into Invoke-Command
and building the credential in that script block, and using Start-Process
to start it as a different user. Note that if you needed to elevate for UAC, the code would be different and this code will only work when you don't need UAC elevation:
# We will create the SecureString inside the Invoke-Command block
$password = "********"
# Use of a Dictionary instead of positional arguments and `param` in the block
# is a little trick you can use here.
Invoke-Command -Session $s -ArgumentList @{ Username = $username; Password = $password } {
$cred =
[PSCredential]::new($args.Username, ( $args.Password | ConvertTo-SecureString -AsPlainText -Force ))
# Placeholder for next part
}
So this is the boilerplate for what you want. You send the credentials to the remote server and build it there. How you execute this at # Placeholder for next part
will depend on what exactly you are running:
External Command (executable)
Start-Process
to run the program as the other userStart-Process -Wait -Credential $cred program.exe -ArgumentList "arguments to the program here"
Any cmdlet which accepts a -Credential
parameter or any command which accepts username and password arguments
$args.Username
and $args.Password
to an external command which has username/password parameters directly. The below however exemplifies using this with a cmdlet and the -Credential
parameter.# Note that some cmdlets don't take a credential for whatever reason
# and may have -Username and -Password arguments instead.
Invoke-AdminCommand -Credential $cred -Register -Verbose
Any Function or Cmdlet which does not accept a -Credential
parameter or username/password arguments
# This can be invoked without splatting but am using splatting for readability
$spArgs = @{
Credential = $cred
FilePath = 'powershell.exe' # You can use `pwsh.exe` for PS Core if necessary
ArgumentList = "-Command ""exampleProgram.exe -username $($args.Username) -password $($args.Password)"""
Wait = $true
NoNewWindow = $true
}
Start-Process powershell.exe