PSExec never completes when run inside start-job - powershell

I'm trying to execute a cmd file on a list of 48 computers. I don't want to execute and wait for completion sequentially because each cmd takes about 10 minutes to complete. WinRM isn't an option. Neither is WMI. PSExec is an option....but I can't seem to make it work inside of Start-Job.
I'm doing something like:
$sb = {
param
(
$computer = "serverw01",
$userid = "domain2\serviceid",
$password = 'servicepw',
$command = "cd /d d:\ && updateAll.cmd"
)
d:\eps\pstools\PsExec.exe -u $userid -p $password "\\$($computer)" cmd /c $command
}
foreach ($computer in Get-Content "D:\Data\serverlist.txt") {
Start-Job $sb -ArgumentList $computer
}
This creates a bunch of jobs....but the never complete and if I Receive-Job on any of them i get back
PS> get-job | receive-job -Keep
+ CategoryInfo : NotSpecified: (:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
PsExec v1.98 - Execute processes remotely
Copyright (C) 2001-2010 Mark Russinovich
Sysinternals - www.sysinternals.com
it executes just fine if I run the function like:
& $sb -computer "serverw01"
Initiating script is run in Powershell v2.0 on Server 2008r2 box
I've tried it on a box in domain2 while logged in with a domain admin userid (same result).

Try this for the psexec command, ensuring you include "-d" to not wait for response, and put the computer variable right after psexec:
d:\eps\pstools\psexec "\\$($computer)" /accepteula -u $userid -p $password -d cmd /c $command

This hanging issue occurs on Win2003 and Win2008 servers.
Most people solve this issue with a workaround like echoing and piping so that powershell gets some input from STDIN.
But there exists a solution within powershell. Just start powershell with the option -inputformat none like:
powershell -inputformat none -command ...

please try the -accepteula parameter to psexec
like
d:\eps\pstools\PsExec.exe -accepteula -u $userid -p $password
from

$computerList = Get-Content "D:\Data\serverlist.txt"
$sb =
{
param($name)
}
$computer = $name
$userid = "domain2\serviceid"
$password = 'servicepw'
$command = "cd /d d:\ && updateAll.cmd"
d:\eps\pstools\PsExec.exe -u $userid -p $password \\$computer cmd /c $command
{
}
foreach ($computer in $computerLinst) {
Start-Job $sb -ArgumentList $computer
}

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.

How to trigger a bat file on remote server only after the pssession is formed on the server

Write-Host "Welcome to Application Process Start/Stop Dashboard" -ForegroundColor Green
Write-Host "`n" "1) Stop" "`n" "2) Start"
[int]$resp = Read-Host "Choose option 1 or 2 for stopping or starting application process respectively"
if($resp -eq 1)
{
[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
$result = [System.Windows.Forms.MessageBox]::Show('Are you sure you want to STOP ?', "Info" , 4 )
if ($result -eq 'Yes')
{
$user = "NAmarshmellow"
$server = "Desktop_10U"
$storesess = New-PSSession -ComputerName $server -Credential $user
Enter-PSSession -Session $storesess
$path = "\\Users\mellow\Documents\Proj"
$pwd = Read-Host -AsSecureString
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)
$value = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
NET USE $path /user:$user $value
Start-Process cmd -ArgumentList "/C C:\Users\Desktop_10U\Documents\Some\Stop.bat" -Wait
Clear-Variable storesess
Exit-PSSession
}
}
I want to trigger a bat file which has some commands that will stop a specific application process. To stop this application process there are specific commands which requires triggering the cmd file on a network drive. So I have written a code which will form PSSession and after the PSSession is formed only then the NET USE command should run. If I first form the PSSession and then trigger the command manually fire the NET USE command then it works fine. But when I trigger the code as a whole it doesn't run fine it throws below error.
NET : System error 1219 has occurred.
At line:18 char:1
+ NET USE $path /user:$user $value
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (System error 1219 has occurred.:String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the server or shared
resource and try again.
The issue is that Enter-PSSession only works via an interactive prompt. aka. You typing in commands into a prompt. A script/running everything together is not interactive (i.e. you can't start entering in commands into the middle of the running script).
The work around is to use Invoke-Command and place everything you want to perform in a script block. This way can be executed as non-interactive commands. e.g.:
....
$user = "NAmarshmellow"
$server = "Desktop_10U"
$path = "\\Users\mellow\Documents\Proj"
$pwd = Read-Host -AsSecureString
$bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($pwd)
$value = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
$script = {
$user = $Using:user
$path = $Using:path
$value = $Using:value
NET USE $path /user:$user $value
Start-Process cmd -ArgumentList "/C C:\Users\Desktop_10U\Documents\Some\Stop.bat" -Wait
}
Invoke-Command -ComputerName $server -Credential $user -ScriptBlock $script
....

Redirecting .bat output when using Invoke-Command

I am trying to redirect the output of a .bat script to a file. The script is run on another machine.
The commented line works. The t.txt file is produced in the expected location. I cannot convince PowerShell to produce the output file when the ScriptBlock is used.
The current result is that the $sb text is printed to the PowerShell console running this script. No file is produced on SERVER2. What do I need to get the output written to the file specified in the scriptblock?
$cn = 'SERVER2'
$Logfile = "D:\DBA\Scripts\monlogs\monlog_$(Get-Date -Format 'yyyy-MM-ddTHH-mm-ss').txt"
$sb = [scriptblock]::Create("{ & cmd.exe /C D:\DBA\Scripts\mon_test_001.bat >`"$Logfile`" }")
### Invoke-Command -ComputerName $cn -ScriptBlock { & D:\DBA\Scripts\mon_test_001.bat >D:\DBA\Scripts\monlogs\t.txt 2>&1 }
Invoke-Command -ComputerName $cn -ScriptBlock $sb
EDIT
After BenH's comment, I found the following to work as expected. Note that the parameter needed to have the $ escaped.
$sb = [scriptblock]::Create("param(`$Logfile) & cmd.exe /C D:\DBA\Scripts\mon_test_001.bat >`"$Logfile`"")
Rather than class create method, maybe casting would work? Then because you're running the scriptblock on a remote machine, use the "$using:" scope on the local variable. (PSv3+ onwards)
$cn = 'SERVER2'
$Logfile = "c:\temp\$(Get-Date -Format 'yyyy-MM-ddTHH-mm-ss').txt"
[scriptblock]$sb = { & cmd.exe /C c:\temp\test.bat > "$using:Logfile" }
Invoke-Command -ComputerName $cn -ScriptBlock $sb
Otherwise for earlier versions, you will need to use a param block and -ArgumentList:
[scriptblock]$sb = {param($logpath) & cmd.exe /C c:\temp\test.bat > "$logpath" }
Invoke-Command -ComputerName $cn -ScriptBlock $sb -ArgumentList $Logfile

Powershell command-line with Autologon.exe

Has anyone made the 'Autologon.exe for Windows v3.10' work with PowerShell v5.1?
Execution 1:
As administrator the following is run:
.\Autologon.exe -n guest10 -d test.com -p Password1 -accepteula yes
Error 1:
Execution 2:
As administrator in powershell the following is run:
.\Autologon.exe guest10 test.com Password1
Error2: Nothing happens
Execution 3:
As administrator in powershell the following is run:
$obj=.\Autologon.exe
$name ="guest10"
$domain="test"
$pass="Password1"
& $obj $name $domain $pass
Error3:
The expression after '&' in a pipeline element produced an object that was not valid. It must result in a command name, a script block, or a CommandInfo object.
I generally use Start-Process with the ArgumentList parameter to run programs with arguments:
$autologon = "C:\folder\Autologon.exe"
$username = "guest10"
$domain = "domain"
$password = "Password1"
Start-Process $autologon -ArgumentList $username,$domain,$password
Or you can put them directly into the command:
Start-Process "C:\folder\Autologon.exe" -ArgumentList "guest10","domain","Password1"
This worked for me:
Start-Process -FilePath $exePath -ArgumentList "/accepteula", $user, $domain, $password -Wait
It's very picky about quote placement.

Starting Powershell elevated from PSExec (enable-psremoting)

I'm trying to enable-psremoting with PSexec on my servers with the following command:
psexec.exe \\server cmd /c "echo . | powershell (-verb runas -argumentlist (enable-psremoting -force))"
but it doesn't work. I'm guessing I'm messing up my double quotes. Any help?
Sune:)
Thanks for commenting all! I found out how to do it, and this is the completed code:
$user = "youruser"
$p = Read-Host "Enter domain password for $adminuser"
cls
$expression1 = "enable-psremoting -force"
$commandBytes1 = [System.Text.Encoding]::Unicode.GetBytes($expression1)
$encodedCommand1 = [Convert]::ToBase64String($commandBytes1)
$expression2 = "Set-ExecutionPolicy remotesigned -Force”
$commandBytes2 = [System.Text.Encoding]::Unicode.GetBytes($expression2)
$encodedCommand2 = [Convert]::ToBase64String($commandBytes2)
$expression3 = "Restart-Service winrm”
$commandBytes3 = [System.Text.Encoding]::Unicode.GetBytes($expression3)
$encodedCommand3 = [Convert]::ToBase64String($commandBytes3)
foreach ($server in (get-content c:\temp\enablepsremotinglist.txt))
{
echo " "
echo "Running on $server"
echo "--------------------------------------- "
echo " "
psexec.exe \\$server -h -u no\$user -p $p cmd /c "echo . | powershell -EncodedCommand $encodedCommand1"
psexec.exe \\$server -h -u no\$user -p $p cmd /c "echo . | powershell -EncodedCommand $encodedCommand2"
psexec.exe \\$server -h -u no\$user -p $p cmd /c "echo . | powershell -EncodedCommand $encodedCommand3"
}
I hope this can be of help to someone else one day:)
PS: Please keep in mind that this send your adminpassword as clear text..
It looks like you are trying to invoke PowerShell to run elevated. This might not be possible to do remotely... I was able to get this to work against a machine without UAC enabled (2003 server):
$c = Get-Credential
$u = $c.UserName
$p = $c.GetNetworkCredential().Password
$path = "C:\SysinternalsSuite"
& "$path\psexec.exe" \\server -u $u -p $p powershell.exe -Command "Enable-PSRemoting -Force"
For some reason though I had to press enter a couple times on the shell for it to keep spitting out output and eventually return me to a prompt. Not sure what's up with that...
You don't need PSExec for that. Check this script by PowerShell developer Lee.
http://poshcode.org/2141