Executing a Batch file on remote server from powershell command - powershell

I'm currently trying to implement the functionality mentioned in this question, but I haven't had any success. I'm trying to run this on a TeamCity server to copy some files and then run a batch file. The robocopy works perfectly but the execution for the Invoke-Command seems to fail but it doesn't throw any error in any of the ways I've tried.
This is my code:
$deploymentComputerName = 'server.name.com'
$robocopyPath = 'C:\Windows\System32\robocopy.exe'
try {
$deploymentShare = "\\" + $deploymentComputerName + "\folder"
$deploymentPackageDirectory = 'c:\local\route'
$invokeRoute = "C:\Route\batchFile.bat"
&$robocopyPath /E /PURGE $deploymentPackageDirectory $deploymentShare /r:0 /XF "LibSassHost.dll" "LibSassHost.Native-64.dll" "libsass.dll"
Write-Host "Attempting to run it with the FIRST method"
Invoke-Command -ComputerName $deploymentComputerName -ScriptBlock {Invoke-Expression -Command:"cmd.exe /c '$invokeRoute'"}
Invoke-Command -ComputerName $deploymentComputerName -ScriptBlock {Invoke-Expression -Command:"cmd.exe /c $invokeRoute"}
Write-Host "Attempting to run it with the SECOND method - " $invokeRoute
Invoke-Command -ScriptBlock { "$deploymentShare\batchFile.bat" } -ComputerName $deploymentComputerName
Write-Host "Finished trying to run the methods"
} catch {
Write-Host "Error running powershell file"
}

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.

Running a script on a virtual machine using Invoke-Command

I wrote this code:
$vmName = $args[0]
$sign_check_tool = $args[1]
$arguments = $args[2]
$remote_session = New-PSSession -VMName $vmName -Credential $cred
try {
Invoke-Command -Session $remote_session -Block {
$signcheck_output = ./$using:sign_check_tool /accepteula -c $using:arguments
Write-Output "${signcheck_output }"
}
} catch [Exception] {
Remove-PSSession $remote_session
exit 1
}
Exit-PSSession
I want to run this script for several sign check tools that I receive as a parameter, and for different installers. But I get this error:
The term './$using:sign_check_tool' is not recognized as the name of a cmdlet, function, file, or operable
I want to pass as a parameter several types of tools to run for the same installer but I get the previous error. If you could help me, I would be grateful.
I think you need to pass $using:sign_check_tool to -ArgumentList to be picked up, for example:
Invoke-Command -Session $remote_session -Block -ArgumentList $using:sign_check_tool, $using:arguments {
param($tool, $args)
$signcheck_output = ./$tool /accepteula -c $args
Write-Output "${signcheck_output }"
}

Powershell Invoke-Command with file share

I have a Powershell script like this:
param([string]$zipFileLocation)
if([string]::IsNullOrEmpty($zipFileLocation))
{
write-host "No location provided"
return $false
}
write-host "Location of zip: $zipFileLocation"
write-host "Test-path result: $(test-path($zipFileLocation))"
write-host "END OF SCRIPT"
When I run this script locally, with as argument '\Server\Share', Test-Path returns true and all works fine. I.e. when I run '.\TestScript.ps1 -zipFileLocation '\\Server\Share' it works as expected.
However, if I run the command like this:
$remsession1 = New-PSSession -credential $credential -ComputerName $ServerName
$UnzipPackageResult= Invoke-Command -session $remsession1 -FilePath C:\Test\TestScript.ps1 -ArgumentList '\\Server\Share'
Then the 'test-path' in the script comes back with $false and an UnauthorizedAccessException error. I do not see why.
The provided credentials do have administrator rights on the server.
Any thoughts?

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 script executing batch script on remote server

I am executing a PowerShell script which executes a batch script at remote server. But in PowerShell script I am not able to handle any failure that could occur in batch script. The batch script is having exit %ERROR_CODE% at the end.
Please let me know how I can catch any error occurred in batch script in the calling PowerShell script.
My PowerShell script is like:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
$command = "cmd.exe /c DbBackupBatch.cmd " + $FullBackupPath + " " + $CustName
$script = 'Invoke-Expression -Command "' + $command + '"'
$scriptblock = [scriptblock]::Create($script)
try {
Invoke-Command -ComputerName $DBServer -Authentication NegotiateWithImplicitCredential -ErrorAction Stop -ScriptBlock $scriptblock
exit 0
} catch {
$message = $_.Exception.Message
Write-Host $_.Exception.Message
# While executing a Java programs, we get message as below -
# Picked up JAVA_TOOL_OPTIONS: -Xms512m -Xmx512m
# This message is treated as error message by PowerShell, though it is not an error
if (($message.Length -lt 50) -and ($message.Contains('Picked up JAVA_TOOL_OPTIONS:'))) {
exit 0
} else {
Write-Host $_.Exception.Message
exit 1
}
}
Give this a whirl:
$remoteReturnValue = Invoke-Command -ComputerName "DV1IMPSSDB01" -Authentication NegotiateWithImplicitCredential -ScriptBlock {
$cmd = Start-Process "cmd.exe" -Wait -PassThru -ArgumentList "/c timeout 5"
$cmdExitCode = $cmd.ExitCode
if ($cmdExitCode -eq 0) {
return "Success"
}
else {
return "Wuh-oh, we have had a problem... exit code: $cmdExitCode"
}
}
Write-Host $remoteReturnValue -ForegroundColor Magenta
Whatever you're trying to do in PowerShell, Invoke-Expression is practically always the wrong approach. PowerShell can execute batch files all by itself, so you can run DbBackupBatch.cmd directly, without Invoke-Expression and even without cmd /c.
Try something like this:
$DBServer = $args[0]
$CustName = $args[1]
$FullBackupPath = $args[2]
try {
Invoke-Command -ComputerName $DBServer -ScriptBlock {
$output = & DbBackupBatch.cmd $args[0] $args[1] 2>&1
if ($LastExitCode -ne 0) { throw $output }
} -ArgumentList $FullBackupPath, $CustName -Authentication NegotiateWithImplicitCredential
} catch {
Write-Host $_.Exception.Message
exit 1
}
exit 0