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)
}
Related
I have a Powershell script which creates a session to a virtual machine. In this remote session I want the machine to read data from a json file and return a custom object from that data, hence I am using the -AsHashtable flag for the ConvertFrom-Json command to do this. However this flag was introduced in Powershell 6 and when I write the powershell version from the remote session to the output the session displays "5.1" as powershell version...
The virtual machine has Powershell 7.2.5 installed and when manually logging onto the vm and launching Powershell 7 and executing the commands it operates as expected. How do I tell the session in my script to use Powershell 7?
This code assumes that the PowerShell ZIP package has been downloaded and unzipped to the remote computer's C:\Users\Public\PSv7\ path. If PowerShell 7 is installed with an installer on the remote computer, then C:\Users\Public\PSv7\pwsh.exe can probably be reduced to pwsh. The -ExecutionPolicy RemoteSigned is probably not needed, but I would recommend keeping -NoProfile for performance.
$ComputerName = 'RemotePCName'
$Temp = Invoke-Command -ComputerName $ComputerName -ScriptBlock {
if($PSVersionTable.PSVersion.Major -lt 7) {
return (C:\Users\Public\PSv7\pwsh.exe -NoProfile -ExecutionPolicy RemoteSigned $MyInvocation.MyCommand.ScriptBlock)
}
else {
return "[$($Env:ComputerName)] {$($PSVersionTable.PSVersion)}"
}
}
Write-Host "$Temp"
The code:
Runs a ScriptBlock on the remote system, saving result to $Temp.
In ScriptBlock: If the current PowerShell version is less than 7, then call pwsh passing self's ScriptBlock.
In ScriptBlock: In Else block, return the current machine's name and PowerShell version as a string.
After exit of ScriptBlock, Write $Temp to Host, showing the machine name and PowerShell version where the else part of the script was ran.
Commands, such as ConvertFrom-Json -AsHashtable, should run fine inside the else statement.
This is the first time I tried passing a ScriptBlock to pwsh.exe from within PowerShell, so, it appears to work, but I'm not sure how pwsh.exe is receiving the ScriptBlock or returning info on exit. I wouldn't be surprised if only [string] values can be returned from pwsh.exe.
Basic Parameter Version:
This version flips the if statement so the else part is where pwsh is called, and accepts any arguments.
$ComputerName = 'RemotePCName'
$MyData1 = 'ArbitraryData1'
$MyData2 = 'ArbitraryData2'
$Temp = Invoke-Command -ComputerName $ComputerName -ArgumentList $MyData1, $MyData2 -ScriptBlock {
if($PSVersionTable.PSVersion.Major -ge 7) {
return "[$($Env:ComputerName)] {$($PSVersionTable.PSVersion)} ($($args[0]), $($args[1]))"
}
else {
return (C:\Users\Public\PSv7\pwsh.exe -NoProfile -ExecutionPolicy RemoteSigned $MyInvocation.MyCommand.ScriptBlock -args $args)
}
}
Write-Host "$Temp"
Defined Parameter Version:
This version is the similar to the above basic parameter version, except the parameters are explicitly defined.
$ComputerName = 'RemotePCName'
$MyData1 = 'ArbitraryData1'
$MyData2 = 'ArbitraryData2'
$Temp = Invoke-Command -ComputerName $ComputerName -ArgumentList $MyData1, $MyData2 -ScriptBlock {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Param1,
[Parameter(Mandatory = $true, Position = 1)]
[string]$Param2
)
if($PSVersionTable.PSVersion.Major -ge 7) {
return "[$($Env:ComputerName)] {$($PSVersionTable.PSVersion)} ($Param1, $Param2)"
}
else {
return (C:\Users\Public\PSv7\pwsh.exe -NoProfile -ExecutionPolicy RemoteSigned $MyInvocation.MyCommand.ScriptBlock -args $Param1, $Param2)
}
}
Write-Host "$Temp"
Update:
Interesting! At the time of creating the above examples I was largely throwing things at the wall and seeing what sticks. But, when looking into -Command, in it I found the following interesting statements:
The Command parameter only accepts a script block for execution when
it can recognize the value passed to Command as a ScriptBlock type.
This is only possible when running pwsh from another PowerShell host.
and:
When called from within an existing PowerShell session, the results
are returned to the parent shell as deserialized XML objects, not live
objects.
Haven't done any experiments yet, but this gives me hope that complex objects can be returned from a call to pwsh.exe.
Updated Alternative Version:
Having given some thought to how the above examples work, I realized that a script block can contain another script block. So, in this example the outer script block is ran on the remote system, and the inner script block, without any concern for what version it is currently running in, is passed to pwsh.exe. A hash table is created, returned, saved to $Temp, and then Format-Table is used to show its content. This means that complex objects can be returned to the calling script.
As with the very first example, this code assumes the PowerShell Zip package is copied to the PSv7 in the Public user profile, but if version 7 is installed, then can probably be reduced to pwsh.
$ComputerName = 'RemotePCName'
$MyData1 = 'ArbitraryData1'
$MyData2 = 'ArbitraryData2'
$Temp = Invoke-Command -ComputerName $ComputerName -ArgumentList $MyData1, $MyData2 -ScriptBlock {
return C:\Users\Public\PSv7\pwsh.exe -NoProfile -args $args {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$Param1,
[Parameter(Mandatory = $true, Position = 1)]
[string]$Param2
)
return #{
ComputerName = $Env:ComputerName
PSVersion = $PSVersionTable.PSVersion
Param1 = $Param1
Param2 = $Param2
}
}
}
$Temp | Format-Table
I have packaged an application with the App Deployment Toolkit.
I install the application with the command:
Start-Process -FilePath $setupx64 -ArgumentList "/silent /install" -verb runas
That works. But only if the installation is running in the user context and not in the system context. If the installation runs in the system context (via SCCM) I need this command without the parameter -verb runas => otherwise it does not work
Start-Process -FilePath $setupx64 -ArgumentList "/silent /install"
How can I check in my script if the installation is started in the user context or in the system context?
Thank you!!
BR
Here is an example snippet of code to determine if the current security context is NT AUTHRORITY\SYSTEM:
if ($env:USERNAME -eq "$env:COMPUTERNAME$") {
# do the thing that's special for the system
}
else {
# do whatever you want for the user
}
Do you mean, "How can I tell if the user is running as Administrator or not?"
If so, the following code should work.
$StartProcessSplat = #{
FilePath = $setupx64
ArgumentList = #(
"/silent",
"/install"
)
}
$User = [Security.Principal.WindowsIdentity]::GetCurrent()
$isAdmin = (New-Object Security.Principal.WindowsPrincipal $User).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)
If ($isAdmin) { $StartProcessSplat['verb'] = 'runas' }
Start-Process #StartProcessSplat
I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?
There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job
The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.
Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode
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%
I'm trying to run a program from PowerShell, wait for the exit, then get access to the ExitCode, but I am not having much luck. I don't want to use -Wait with Start-Process, as I need some processing to carry on in the background.
Here's a simplified test script:
cd "C:\Windows"
# ExitCode is available when using -Wait...
Write-Host "Starting Notepad with -Wait - return code will be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru -Wait)
Write-Host "Process finished with return code: " $process.ExitCode
# ExitCode is not available when waiting separately
Write-Host "Starting Notepad without -Wait - return code will NOT be available"
$process = (Start-Process -FilePath "notepad.exe" -PassThru)
$process.WaitForExit()
Write-Host "Process exit code should be here: " $process.ExitCode
Running this script will cause Notepad to be started. After this is closed manually, the exit code will be printed, and it will start again, without using -wait. No ExitCode is provided when this is quit:
Starting Notepad with -Wait - return code will be available
Process finished with return code: 0
Starting Notepad without -Wait - return code will NOT be available
Process exit code should be here:
I need to be able to perform additional processing between starting the program and waiting for it to quit, so I can't make use of -Wait. How can I do this and still have access to the .ExitCode property from this process?
There are two things to remember here. One is to add the -PassThru argument and two is to add the -Wait argument. You need to add the wait argument because of this defect.
-PassThru [<SwitchParameter>]
Returns a process object for each process that the cmdlet started. By default,
this cmdlet does not generate any output.
Once you do this a process object is passed back and you can look at the ExitCode property of that object. Here is an example:
$process = start-process ping.exe -windowstyle Hidden -ArgumentList "-n 1 -w 127.0.0.1" -PassThru -Wait
$process.ExitCode
# This will print 1
If you run it without -PassThru or -Wait, it will print out nothing.
The same answer is here: How do I run a Windows installer and get a succeed/fail value in PowerShell?
It's also worth noting that there's a workaround mentioned in the "defect report" link above, which is as following:
# Start the process with the -PassThru command to be able to access it later
$process = Start-Process 'ping.exe' -WindowStyle Hidden -ArgumentList '-n 1 -w 127.0.0.1' -PassThru
# This will print out False/True depending on if the process has ended yet or not
# Needs to be called for the command below to work correctly
$process.HasExited
# This will print out the actual exit code of the process
$process.GetType().GetField('exitCode', 'NonPublic, Instance').GetValue($process)
While trying out the final suggestion above, I discovered an even simpler solution. All I had to do was cache the process handle. As soon as I did that, $process.ExitCode worked correctly. If I didn't cache the process handle, $process.ExitCode was null.
example:
$proc = Start-Process $msbuild -PassThru
$handle = $proc.Handle # cache proc.Handle
$proc.WaitForExit();
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Two things you could do I think...
Create the System.Diagnostics.Process object manually and bypass Start-Process
Run the executable in a background job (only for non-interactive processes!)
Here's how you could do either:
$pinfo = New-Object System.Diagnostics.ProcessStartInfo
$pinfo.FileName = "notepad.exe"
$pinfo.RedirectStandardError = $true
$pinfo.RedirectStandardOutput = $true
$pinfo.UseShellExecute = $false
$pinfo.Arguments = ""
$p = New-Object System.Diagnostics.Process
$p.StartInfo = $pinfo
$p.Start() | Out-Null
#Do Other Stuff Here....
$p.WaitForExit()
$p.ExitCode
OR
Start-Job -Name DoSomething -ScriptBlock {
& ping.exe somehost
Write-Output $LASTEXITCODE
}
#Do other stuff here
Get-Job -Name DoSomething | Wait-Job | Receive-Job
The '-Wait' option seemed to block for me even though my process had finished.
I tried Adrian's solution and it works. But I used Wait-Process instead of relying on a side effect of retrieving the process handle.
So:
$proc = Start-Process $msbuild -PassThru
Wait-Process -InputObject $proc
if ($proc.ExitCode -ne 0) {
Write-Warning "$_ exited with status code $($proc.ExitCode)"
}
Or try adding this...
$code = #"
[DllImport("kernel32.dll")]
public static extern int GetExitCodeProcess(IntPtr hProcess, out Int32 exitcode);
"#
$type = Add-Type -MemberDefinition $code -Name "Win32" -Namespace Win32 -PassThru
[Int32]$exitCode = 0
$type::GetExitCodeProcess($process.Handle, [ref]$exitCode)
By using this code, you can still let PowerShell take care of managing redirected output/error streams, which you cannot do using System.Diagnostics.Process.Start() directly.
Here's a variation on this theme. I want to uninstall Cisco Amp, wait, and get the exit code. But the uninstall program starts a second program called "un_a" and exits. With this code, I can wait for un_a to finish and get the exit code of it, which is 3010 for "needs reboot". This is actually inside a .bat file.
If you've ever wanted to uninstall folding#home, it works in a similar way.
rem uninstall cisco amp, probably needs a reboot after
rem runs Un_A.exe and exits
rem start /wait isn't useful
"c:\program files\Cisco\AMP\6.2.19\uninstall.exe" /S
powershell while (! ($proc = get-process Un_A -ea 0)) { sleep 1 }; $handle = $proc.handle; 'waiting'; wait-process Un_A; exit $proc.exitcode