Powershell remote script error Error during CryptAcquireContext - powershell

I just run a simple script with invoke-command -computer [servername] -scriptblock {powershell.exe D:\test\script.ps1}
If I run the script manually in the box and then run the remote script again the error does not appear anymore but I don't like having to login manually to the box and run the script to be able fix this error especially with so many servers. Can anyone help me on this. Thanks
Error during CryptAcquireContext. [servername] :
Error msg : The requested operation cannot be completed. The computer must be trusted for delegation and the current user account must be configured to allow delegation.
Error code : 80090345
The script running on the server that gets the error part
$fciv = "D:\test\fciv.exe"
$fcivLog = "D:\test\fcivLog.txt"
$xmlPath = "D:\test\server.xml"
& $fciv -v -bp "\\servername\folder1" -XML $xmlPath | Out-File $fcivLog

Here is a PowerShell function, that should work on PowerShell version 2.0, to calculate MD5 hashes:
function Get-MD5FileHash {
[CmdletBinding()]
param (
[string] $Path
)
$MD5 = [System.Security.Cryptography.MD5]::Create();
$Stream = [System.IO.File]::OpenRead($Path);
$ByteArray = $MD5.ComputeHash($Stream);
[System.BitConverter]::ToString($ByteArray).Replace('-','').ToLower();
$Stream.Dispose();
}
Get-MD5FileHash -Path C:\test\test.xlsx;
I tested it out on PowerShell 4.0 on Windows 8.1, and it works great!

This question is quite old, and a work around has been found. But it still does not resolve the primary issue of delegation for programs using CryptAcquireContext
I had the very same problem with another program (BiosConfigUtility, from HP).
I solved it by allowing delegation between my computer, and remote computers.
To enable delegation on your client :
Enable-WSManCredSSP -Role Client -DelegateComputer host.domain.com -Force
To enable delegation on the remote computer :
Enable-WSManCredSSP -Role Server –Force
See this post : https://devblogs.microsoft.com/scripting/enable-powershell-second-hop-functionality-with-credssp/ for more info

You can always use scheduled tasks instead. This script changes the bios from legacy to uefi boot using biosconfigutility64 (or erase setup password for surplusing). Remotely running it directly will give that cryptacquirecontext error.
# usage: .\hpuefi.ps1 comp1,comp2,comp3
$s = new-pssession $args[0]
$src = 'Y:\hp-bios-new'
$dst = 'c:\users\admin\documents\hp-bios-new'
icm $s { if (! (test-path $using:dst)) { mkdir $using:dst > $null } }
$s | % { copy $src\biosconfigutility64.exe,$src\pass.bin,$src\uefi.bat,$src\uefi.txt $dst -tosession $_ }
icm $s {
# ERROR: Error during CryptAcquireContext. LastError = 0x80090345
# & $using:dst\uefi.bat
# 2>&1 must go last
$action = New-ScheduledTaskAction -Execute 'cmd' -argument '/c c:\users\admin\documents\hp-bios-new\uefi.bat > c:\users\admin\documents\hp-bios-new\uefi.log 2>&1'
Register-ScheduledTask -action $action -taskname uefi -user system > $null
Start-ScheduledTask -TaskName uefi
# wait
while ((Get-ScheduledTask -TaskName uefi).State -ne 'Ready') {
Write-Verbose -Message 'Waiting on scheduled task...' }
Get-ScheduledTask uefi | Get-ScheduledTaskInfo | ft
# remove-scheduledtask uefi
# shutdown /r /t 0
}
uefi.bat:
%~dp0BiosConfigUtility64.exe /set:"%~dp0uefi.txt" /cspwdfile:"%~dp0pass.bin"
exit /b %errorlevel%

Related

Trying to catch the exitcode from PowerShell Invoke-command with a BAT file

I'm trying to catch the exitcode from a PowerShell script that uses a Invoke-Command to run a scriptblock on a remote machine.
First the BAT file:
The BAT file is run with a variable. The script looks like this:
powershell.exe -noninteractive -noprofile -command "& {E:\Scripts\Check-Services_XXX.ps1 %1 }"
EXIT /B %errorlevel%
The PowerShell script looks like this:
param(
[string] $ip #IP address van server
)
$username = "DOMAIN\DOMAIN_USER"
$secpasswdfile = "E:\Location\DOMAINUSER_encrypted_password.txt"
$secpasswd = Get-Content $secpasswdfile | ConvertTo-SecureString
$credentials = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)
$soptions = New-PSSessionOption -SkipCACheck -SkipRevocationCheck -SkipCNCheck
Invoke-Command -ComputerName $ip -UseSSL -SessionOption $soptions -Credential $credentials -ScriptBlock `
{
# Start services
Start-Service -InputObject (Get-Service -Name IAS)
# Check services status
$checkservice = (get-service -Name IAS -ErrorAction SilentlyContinue)
if($checkservice.status -ne "Running"){$host.SetShouldExit(1)}
exit
}
The problem is that the ExitCode is not captured back, so when the BAT file ends, it ends with 0. That would be the case if everything is running. But i deliberately changed the service name in the check service section to something that does not exist for sure, but still it the BAT file ends with Exitcode 0
Done so far: Tried this solution:
catching return code of a command with "invoke-command" - Powershell 2
But didn't work: got the following error "is not equal to Open, you cannot run a command in the session. The session state is Closing"
Apparently, when it exited with a error, the session was closed, thus couldn't get the exitcode
Also tried this one: Capture Write-Host output and exit code from Invoke-Command on a Remote System
But also the same result; no correct exitcode (expected 1 instead of 0 in the BAT file)
SOLUTION!
Thanks to #js2010 and #mklement0 ; it works now like a charm!
This is the BAT file:
powershell.exe -noprofile -File "E:\Scripts\Check-Services_XXX.ps1" "%1" "%2"
EXIT /B %errorlevel%
And here is the PowerShell code that eventually worked out for me:
param(
[string] $ip, #IP address of checked server
[string] $service ) #Service name
$username = "DOMAIN\USER"
$secpasswdfile = "E:\Scripts\Credentials\DOMAIN-USER_encrypted_password.txt"
$secpasswd = Get-Content $secpasswdfile | ConvertTo-SecureString
$credentials = New-Object System.Management.Automation.PSCredential ($username, $secpasswd)
$soptions = New-PSSessionOption -SkipCACheck -SkipRevocationCheck -SkipCNCheck
$session = New-PSSession -ComputerName $ip -UseSSL -SessionOption $soptions -Credential $credentials
# Start services
Invoke-Command -Session $session -ScriptBlock { Start-Service -Name $using:service }
# Check services status
$checkservice = Invoke-Command -Session $session { Get-Service -name $using:service | where status -eq running }
if (! $checkservice) {
write-output ("Error 1, Service '" + $service + "' not running or not found.")
exit 1
}
I had some issues with passing variables to remote commands, this link helped me out (https://powershellexplained.com/2016-08-28-PowerShell-variables-to-remote-commands/)
You would have to run the exit command outside of invoke-command.
# check-service.ps1
$result = invoke-command localhost { get-service appxsvc |
where status -eq running }
if (! $result) {
exit 1
}
Change your invocation of powershell.exe to use the -File CLI parameter:
powershell.exe -NoProfile -File "E:\Scripts\Check-Services_XXX.ps1" "%1"
EXIT /B %errorlevel%
That way, the .ps1 script's exit code is properly relayed as powershell.exe's exit code.
Additionally, as js2010's answer notes, you'll need to use your $host.SetShouldExit(1) call out of the Invoke-Command script block, given that the latter executes remotely. For the reasons explained below, exit 1 is preferable.
Generally speaking:
There's no reason to use the -Command (-c) CLI parameter with "& { ... }" in order to invoke code - just use "..." directly. Older versions of the CLI documentation erroneously suggested that & { ... } is required, but this has since been corrected.
Not only is "& { ... }" unnecessary, it invariably resets the exit code to 0.
As for your use of $host.SetShouldExit(1) to request exiting with an exit code of 1 (leaving aside that in a remote call it isn't effective):
It generally isn't designed to be called from user code, as explained in this answer.
For general information about exit codes in PowerShell, see this answer.

Execute Powershell code on remote computer and then use result on local computer

I know, that to execute code on remote computer, I must use -ArgumentList and param to pass variables to remote host.
Can anyone hint me, how to execute code on remote computer, and then use result on local computer?
My code, to determine free space on remote computer, and, depending on result - fail build, if remote host has not enough space:
$securePassword = ConvertTo-SecureString $env:password -AsPlainText -force
$credential = New-Object System.Management.Automation.PsCredential("$env:login",$securePassword)
Invoke-Command -ComputerName $env:SERVER -credential $credential -scriptblock {
$freespace = Get-PSDrive D
echo $freespace.Free
$DiskDSpace = ($freespace.Free)
If ($DiskDSpace -lt 214748364809999999999999) {
echo "Free space on disk D is less than 20 GB"
$LastExitCode = 1
exit $LastExitCode
}
}
Problem, is that $DiskDSpace variable value only present on remote computer and Jenkins fails build only, if "$LastExitCode = 1" & "exit $LastExitCode" executed outside "-scriptblock {"
$diskSpace = Invoke-Command -ComputerName $env:SERVER -credential $credential -scriptblock {
$freespace = Get-PSDrive D
echo $freespace.Free
$DiskDSpace = ($freespace.Free)
If ($DiskDSpace -lt 214748364809999999999999) {
echo "Free space on disk D is less than 20 GB"
$LastExitCode = 1
exit $LastExitCode
}
}
Note the $diskSpace before the Invoke-Command. This should do the trick!

Import-PSSession - throwing credentials-mask

I got a problem with a powershell-script used in our domain to create new users:
Helpdesk will call a .bat as administrator, this bat calls a script-file to automate the creation. In this script, two sessions are created and imported, to use the Exchange- and AD-cmdlets locally.
During/after the import a second/third credential-mask gets thrown, but clicking "cancel" will do nothin, the script will run through without any issues. Nevertheless this annoys the helpdesk..
When running the .ps1 directly from the ISE, the mask won't be shown. Also, when C&Ping the Create-/Import part of the script to a new file and calling it the same way as before also won't show these mask..
Here a part of the .ps1-file:
<#
.DESCRIPTION
Creates a new standard user
.NOTES
Requires : Exchange 2016 Remote Session
Req.OS Version : not tested
Req.PS Version : not tested
.EXAMPLE
Create-User.ps1 -datapath \\path\to\userdata.csv -credentialobject $cred
#>
Param (
[string]$datapath, <#Folder where the CSVs sit #>
[System.Management.Automation.CredentialAttribute()]$credentialobject = $null
)
#region SET global var definitions
$ErrorActionPreference = "Continue"
Start-Transcript -path $ScriptLogPath # | out-null
#endregion
#region SET var definitions
$userfile = "$datapath\userdata.txt"
$groupfile = "$datapath\groupdata.txt"
#Exchange
$MSXremotingserver = "exchangehostname"
$MSXdatabasenames = #("msx_db")
#AD
$domaincontroller = "dchostname"
$ADremotingserver = $domaincontroller
$BaseDN = "OU=Users,DC=domain,DC=local"
#endregion
#region Import Userdata
# CSV's are getting imported here - WORKING
#endregion
#region INIT Remotesession
#Get AD Creds / use given AD Creds
if (($credentialobject -ne $null) -and (($credentialobject.GetType()).name -eq "PSCredential")){
$UserCredential = $credentialobject
}else{
$UserCredential = Get-Credential
# Get credentials to create the remote-sessions. Seems to be working.
}
$MSXSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionURI http://$MSXremotingserver/powershell -Credential $UserCredential
echo "import..."
$null = Import-PSSession $MSXSession -AllowClobber -DisableNameChecking # | out-null
# After the import (Progress bars running through on top of the PS) another credential-mask appearing, "Cancel" makes the script run through without further errors.
echo "OK"
$ADSession = New-PSsession -Computername $ADremotingserver -Credential $UserCredential
Invoke-Command -Command {Import-Module ActiveDirectory -DisableNameChecking} -Session $ADSession # | out-null
echo "import..."
Import-PSSession -Session $ADSession -Module "ActiveDirectory" -Prefix Remote -AllowClobber -DisableNameChecking # | out-null
# After the import (Progress bars running through on top of the PS) another credential-mask appearing, "Cancel" makes the script run through without further errors.
echo "OK"
#AD-user already existing?
if ([bool](get-remoteaduser -LDAPFilter "(SamAccountName=$($userdata.Kuerzel))")){
#Throw custom error - AD-User bereits vorhanden!
}
#build Account...
# AD-user and Mailbox are created and configured. WORKING!
#endregion
#region END Script
Get-PSSession | Remove-PSSession
Stop-Transcript
Write-Host "Beende Skript..."
start-sleep -Seconds 3
exit 10000
#endregion
And here's how the .ps1 is being called:
%systemroot%\System32\WindowsPowerShell\v1.0\powershell.exe -executionpolicy bypass -file \\helpdeskserver\powershell_userdata$\Create-User.ps1 \\helpdeskserver\path\to\csv"
I don't know what to do. Tried many different versions of each command, tried piping the in/output, nothing will do..
Google doesn't seem to know that behaviour, neither anyone here on Stackoverflow..
Thanks for any tips and help, I'll apprechiate!
Regards, Ting3l
Edit: When starting the .bat-file without administrative rights (Or with right-click -> other user.. -> admin-account) the second/third credential-dialog won't appear, instead I get an "Index out of range"-exception.
Maybe it's unimportant, but you can try to exit the session by Exit-PSSession. After that use exit 1000. Becoaser when you use exit in the session it completes the session (wherein all code after will be ignored, but script will have successful completed)

How Do I run Powershell x86 from Powershell?

I read this answer: How to Open Powershell from Powershell
start powershell
This opens the base, large resolution PS instance. How do I open PS(x86)?
Start-Process $Env:WINDIR\SysWOW64\WindowsPowerShell\v1.0\powershell.exe
I recommend Caleb's answer. But personally, I have a function in the PowerShell profile that loads on startup and launches a new PowerShell x86 shell when running x86 as this is so commonly required.
Function x86{
Start-Process $($env:SystemRoot + "\syswow64\WindowsPowerShell\v1.0\powershell.exe")
}
NB: $env:windir and $env:SystemRoot are equivalent here. Maybe not always
For a quick fix I think this solution will help you
start 'C:\Users\(Your-username here)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Windows PowerShell\Windows PowerShell (x86).lnk'
Please note this is just a quick fix.
The following code will Dynamically switch Powershell to run in 64-bit mode
if ($env:PROCESSOR_ARCHITEW6432 -eq "AMD64") {
    write-warning "Y'arg Matey, we're off to 64-bit land....."
    if ($myInvocation.Line) {
        &"$env:WINDIR\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile $myInvocation.Line
    }else{
        &"$env:WINDIR\sysnative\windowspowershell\v1.0\powershell.exe" -NonInteractive -NoProfile -file "$($myInvocation.InvocationName)" $args
    }
exit $lastexitcode
}
 
 
write-host "Main script body"
You will need the complete path to the x86 Powershell executable. If you are launching it from the command prompt (CMD.EXE), you would use
start "" "%SystemRoot%\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
If you were starting it from a PowerShell session, you would use
start "" "$env:SystemRoot\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
or
Start-Process -FilePath "$env:SystemRoot\SysWOW64\WindowsPowerShell\v1.0\powershell.exe"
When I last had to run a 32-bit version of PowerShell it was for something specific (there was no 64-bit version of a DLL that I needed to access, reference: View All Certificates On Smart Card). When that was the case I simply executed the needed code as a background job using the -RunAs32 switch for New-Job. Full code that I ended up using can be found in the referenced question, but the general concepts are:
$RunAs32Bit = {
Do some stuff that requires 32-bit
}
#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
Write-Warning "Non-32bit architecture detected, collecting certificate information in separate 32bit process."
$Job = Start-Job $RunAs32Bit -RunAs32
$SCStore = $Job | Wait-Job | Receive-Job
}Else{
$SCStore = $RunAs32Bit.Invoke()
}
Download PSExec
Then, run this in PowerShell: PATH_TO_PSEXEC\psexec.exe -i powershell
The core structure including passing of parameters in either scenario is given below
Param(
[String] $Param1 =#("Param1"),
[String] $Param2 =#("Param2")
)
$ScriptLocation = Split-Path $script:MyInvocation.MyCommand.Path -Parent
Write-Host $ScriptLocation
$RunAs32Bit = {
Param(
[String] $Param1 =#("Param1"),
[String] $Param2 =#("Param2")
)
...
return $Result
}
#Run the code in 32bit mode if PowerShell isn't already running in 32bit mode
If($env:PROCESSOR_ARCHITECTURE -ne "x86"){
Write-Warning "Non-32bit architecture detected, processing original request in separate 32bit process."
$Job = Start-Job $RunAs32Bit -RunAs32 -ArgumentList ($Param1, $Param2, $ScriptLocation)
$Result = $Job | Wait-Job | Receive-Job
}Else{
$Result = Invoke-Command -ScriptBlock $RunAs32Bit -ArgumentList ($Param1, $Param2, $ScriptLocation)
}

Install program remotely using Invoke-Command

The variable at the top of the script defines several commands/variables for New-PSDrive, as well as connection and installation.
After this, a function is created to open a text file and extract information out of it. I know this part works because I use it in 2 other scripts.
Lastly, The script executes the commands in the first variable.
The script will show as running successfully, but checking the remote computer reveals that nothing happened.
Prior to doing any of this activity, the remote computer has a script run against it that:
enables PSRemoting (setting firewall rules and starting WinRM), and
bypasses execution policies.
After those steps, the script below is run to install a piece of software.
$eAudIT2014V2Install = {
$eAudIT2014V2password = ConvertTo-SecureString "PasswordHere" -AsPlainText -Force
$eAudIT2014V2cred = New-Object System.Management.Automation.PSCredential('domain\user', $eAudIT2014V2password)
$eAudIT2014V2drive = New-PSDrive -Name eAudIT2014V2 -PSProvider FileSystem -Root "\\Server\Share" -Credential $eAudIT2014V2cred
$eAudIT2014V2job = Start-Job {"eAudIT2014V2:\Setup.cmd"}
Wait-Job $eAudIT2014V2job
Receive-Job $eAudIT2014V2job
}
Function Get-OpenFile($initialDirectory) {
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = $initialDirectory
$OpenFileDialog.ShowDialog()
$OpenFileDialog.Filename
$OpenFileDialog.ShowHelp = $true
}
$InputFile = Get-OpenFile
if ($InputFile -eq "Cancel") {
Write-Host "Canceled By User"
exit
} else {
$Computers = #(Get-Content -Path $InputFile)
}
foreach ($computer in $computers) {
Write-Host "Installing eAudIT 2014V2 on Selected Computers"
Invoke-Command $eAudIT2014V2Install
}
I'm noticing that if I tell this script to run something basic like notepad.exe, a dllhost process starts on the machine, but notepad never does. What am I doing wrong?
The answer is pretty simple here. All of your script is for naught if you don't tell the Invoke-Command cmdlet what computer you want to execute the code on. As it is you are simply iterating a loop and invoking that command X number of times on the local machine. You need to change that second to the last line to specify the machine to execute the code on:
Invoke-Command $eAudIT2014V2Install -ComputerName $computer