Running a script on a virtual machine using Invoke-Command - powershell

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 }"
}

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.

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?

Powershell Invoke-Command causes different result

I have a function that is used to purge a message queue on a machine but I'm looking to adapt it and make it a little more robust. I'd like to be able to fire the command to a machine even if the current machine doesn't have MSMQ installed.
The local command works without issue but when the invoke-command is called, the check to see if the queue exists returns false (even though the queue does exist). Has anyone run into anything like this before? Any suggestions?
This is my function:
Function Purge-MessageQueue
{
<#
.Synopsis
Use hostname to purge message queue
.DESCRIPTION
Checks if MSMQ is locally installed otherwise fire the purge
command to the machine you are purging
.EXAMPLE
Purge-MessageQueue -targetMachine $env:computername -queueName 'test'
#>
Param
(
[Parameter(Mandatory=$true)]
[String]$targetMachine,
[Parameter(Mandatory=$true)]
[string]$queueName
)
Write-Verbose "Purging $queueName queue on: $targetMachine"
$queueName = "$targetMachine\$queueName"
$error.clear()
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
try {[void][System.Messaging.MessageQueue]::Exists($queueName)}
catch
{
if ($_.exception.ToString() -like '*Message Queuing has not been installed on this computer.*')
{
#push command to machine
$RemoteSuccess = Invoke-Command -ComputerName $targetMachine -ScriptBlock { Param($queueName)
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{$queue.Purge()}
Catch{$error}
}
} -ArgumentList $queueName
}
}
If(!$error)
{
If([System.Messaging.MessageQueue]::Exists($queueName))
{
$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
$queue.Purge()
}
}
If(!$Error -and !$RemoteSuccess)
{
Write-Host "$queueName queue on $targetMachine cleared"
}
Else
{
Write-Warning "Failed locating queue $queueName on $targetMachine"
}
}
NOTES:
In order to identify what exactly is going on, I used write-host on the exists statement and it returns false. The queue is not being found when I pass the scriptblock. It is executing on the other machine (tested writing a file which succeeded). When I run:
Write-Host "$([System.Messaging.MessageQueue]::Exists($queueName))`n$queueName"
$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
I get the false, the correct queue name, and the following error:
Exception calling "Purge" with "0" argument(s): "The queue does not
exist or you do not have sufficient permissions to perform the
operation."
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : MessageQueueException
+ PSComputerName : XXXXXX
Running the same command directly on the machine works without issue.
I also found someone else trying to do something similar on serverfault:
https://serverfault.com/questions/399178/how-to-retrieve-names-of-all-private-msmq-queues-efficiently
And when I try this:
Invoke-Command -ComputerName $targetMachine -ScriptBlock { Get-MsmqQueue }
I get the following result:
Cannot find specified machine.
+ CategoryInfo : ObjectNotFound: (:) [Get-MsmqQueue], MessageQueueException
+ FullyQualifiedErrorId : MachineNotFound,Microsoft.Msmq.PowerShell.Commands.GetMSMQQueueCommand
This following command does return the data, but it doesn't allow me to send a purge command:
Invoke-Command -ComputerName $targetMachine -ScriptBlock {Get-WmiObject -class Win32_PerfRawData_MSMQ_MSMQQueue}
I also tried to write the content to a script file and then call the file, which when run on the machine, works without issue but not when called via invoke-command:
$filewriter = #"
[Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If([System.Messaging.MessageQueue]::Exists('$queueName'))
{
`$objqueue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queueName
Try{`$objqueue.Purge()}
Catch{`$error}
}
"#
$session = New-PSSession -ComputerName $targetMachine
Invoke-Command -Session $session -ScriptBlock {Param($FileWriter)
$FileWriter | Out-File "C:\Temp\PurgeQueue.ps1"
} -ArgumentList $filewriter
$test = Invoke-Command -Session $session -scriptblock {Pushd "C:\Temp\"
.\PurgeQueue.ps1}
I have not found the cause for this, but I will summarize what I have found and my workaround
Summary:
When invoking msmq commands via invoke-command, only private queues appear and can be manipulated.
Workaround:
I've build a function to deal with purging and adding message to queues by creating scheduled tasks on the remote machine to call the script created by the command.
Function Push-MSMQRemoteCommand
{
Param(
[Parameter(Mandatory=$true)]
$targetMachine,
[Parameter(Mandatory=$true)]
$password,
[Parameter(Mandatory=$true)]
$queueName,
[Switch]$purge,
$user,
$message)
Begin
{
If(!$user){$user = "$env:USERDOMAIN\$env:USERNAME"}
If($purge -and $message.Length -ne 0){Write-Error "Choose to purge or add... not both" -ErrorAction Stop}
$queuepath = "$targetMachine\$queueName"
#build commands to push
If($purge)
{
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
If ([System.Messaging.MessageQueue]::Exists('$queuePath')) {
`$queue = new-object -TypeName System.Messaging.MessageQueue -ArgumentList $queuePath
`$queue.Purge()
}
"#
}
ElseIf($message)
{
If($message.Length -eq 0){Write-Error "No message provided to add message" -ErrorAction Stop}
$scriptblock = #"
[void] [Reflection.Assembly]::LoadWithPartialName("System.Messaging")
`$queue = new-object System.Messaging.MessageQueue "$queuepath"
`$utf8 = new-object System.Text.UTF8Encoding
`$msgBytes = `$utf8.GetBytes('$message')
`$msgStream = new-object System.IO.MemoryStream
`$msgStream.Write(`$msgBytes, 0, `$msgBytes.Length)
`$msg = new-object System.Messaging.Message
`$msg.BodyStream = `$msgStream
`$msg.Label = "RemoteQueueManagerPowershell"
`$queue.Send(`$msg)
"#
}
#Push Commands
Invoke-Command -ComputerName $targetMachine -ScriptBlock {
Param($user,$password,$scriptblock)
$scriptblock | Out-file -FilePath "C:\temp\ManageQueue.ps1" -Force
$action = New-ScheduledTaskAction -execute 'powershell.exe' -Argument '-File "C:\temp\ManageQueue.ps1"'
#scheudling action to start 2 seconds from now
$trigger = New-ScheduledTaskTrigger -Once -At ((Get-Date)+(New-TimeSpan -Seconds 2))
Register-ScheduledTask -TaskName RemoteQueueManager `
-Action $action `
-Trigger $trigger `
-User "$user"`
-Password $password
#Start-Sleep -Seconds 10
Unregister-ScheduledTask -TaskName RemoteQueueManager -Confirm:$false
Remove-Item -Path "C:\temp\ManageQueue.ps1" -Force
} -ArgumentList $user,$password,$scriptblock
}
}
From your analysis I have feeling that it is issue of rights.
Did you check the rights for your user?
If you are a normal user you have to do the following (not an Administrator) on the
destination computer/server/VM:
1) first create a group and add there users
net localgroup "Remote PowerShell Session Users" /add
net localgroup "Remote PowerShell Session Users" the-user /add
2) Invoke GUI
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
3) Add Remote PowerShell Session Users group and grant it execute (invoke) rights
4) Restart the service:
Set-PSSessionConfiguration microsoft.powershell -ShowSecurityDescriptorUI
5) the user now should be able to run remote session
The original source is here.

Powershell - File path, is not a recognized as the name of a cmdlet. function script file

I am getting the following error,
Errors caught - TRAPPED: System.Management.Automation.RemoteException with message TRAPPED: The term 'D:\ServiceNow\RDC-
Dev-All\agent\scripts\PowerShell\ImMigration_script.ps1' is not recognized as the name of a cmdlet, function, script fil
e, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and t
ry again.
The issue appears to be with the invoke-command
Invoke-Command -Session $Session -ScriptBlock $theCommand2
i have tired using -FilePath with no luck.
Also tired passing the command and param separately :
Invoke-Command -Session $Session -ScriptBlock $theCommand2 -argumentlist $leName
I am triggering the script using:
D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell\invokLyncUAdd.ps1 -param1 'CN=lync2013testuser1,CN=Users,DC=test,DC=COMPANY,DC=com' -param2 AD\sys-LyncProATSC -param3 Z0185-XAP0007-S.test.COMPANY.com
###############################################################################
param( $param1, $param2, $param3 )
$ErrorActionPreference = "Stop"
# trap {
# write-output $("TRAPPED: " + $_.Exception.GetType().FullName);
# write-output $("TRAPPED: " + $_.Exception.Message);
# break
#}
$leName = $param1
$leName = ("'" + "$leName" + "'")
$thePath = 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell'
$theCommand = $thePath+"\ImMigration_script.ps1 -param1 $leName"
$theCommand2 = [Scriptblock]::Create($theCommand)
# Write-Host "We use string $theCommand below"
$Account = $param2
$useP = Get-Content $thePath\'Information.txt'
$Prompt = convertto-securestring $useP -AsPlainText -Force
$leHost = $param3
try{
$Credential = new-object -typename System.Management.Automation.PSCredential
-argumentlist $Account, $Prompt
$Timeout = New-PSSessionOption -IdleTimeout 60000
$Session = New-PSSession -ComputerName $leHost -Credential $Credential -
Authentication Credssp -SessionOption $Timeout -ErrorAction Stop
Invoke-Command -Session $Session -ScriptBlock $theCommand2
}
catch
{
$exceptType = $("TRAPPED: " + $_.Exception.GetType().FullName);
$exceptMess = $("TRAPPED: " + $_.Exception.Message);
}
finally
{
if($exceptType) { "Errors caught - $exceptType with message $exceptMess " } }
Any help would be great, Thanks
The session is being executed on the remote computer, and I believe that's where PowerShell will expect the file to exist.
I would approach it by attempting to load the local script as a scriptblock so that it is in memory:
$thePath = 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell'
$theCommand = $thePath+"\ImMigration_script.ps1"
$theCommand2 = [Scriptblock]::Create(Get-Content $theCommand)
Then, from your question:
Invoke-Command -Session $Session -ScriptBlock $theCommand2 -argumentlist $leName
Please let me know if this works.
If the file is in local, then
powershell.exe -noexit -file 'D:\ServiceNow\RDC-Dev-All\agent\scripts\PowerShell\invokLyncUAdd.ps1' -param1 'CN=lync2013testuser1,CN=Users,DC=test,DC=COMPANY,DC=com' -param2 'AD\sys-LyncProATSC' -param3 'Z0185-XAP0007-S.test.COMPANY.com'
If It is in the remote system, then make sure you are mentioning the remote path properly in the invoke-command.

Call a remote script from another with multiple parameters not working

I am trying to create a script that will take input (hardcoded values for now) and call an install PS script and run it on multiple servers. I am using a PSSession and Invoke-Command(see below). The below runs, but does nothing. It doesn't seem to call the other script. Beyond getting it to actually install, I need to know if it was successful or not. I'm pretty novice at Powershell, so any hints/help/suggestions would be great. The below is wrapped in a ForEach to loop the servers with $Computer
Try
{
$session = New-PSSession -ComputerName App02 -Credential $cred
$sourceInstall = $sourceFolder + 'Install\Install.ps1'
Invoke-Command -Session $session -ScriptBlock{param($serviceName, $installFolder, $sourceFolder, $Action, $username, $password) $sourceInstall} -ArgumentList ($ServiceName, $installFolder, $sourceFolder, $Action, $username, $password)
}
Catch
{
$Filename = "Error.txt"
Write-Output "ERROR: Partial Service Deployment. See error log file(s)"
Add-Content $Filename $_.Exception.Message
}
Get-PSSession | Remove-PSSession
You can use it without $Using statement in any version of PowerShell.But pass that too as an argument.
Eg:-
Invoke-Command -ScriptBlock
param($Name)
& $Command $Name
} -ArgumentList 'Get-Process','Notepad'
But you have to pass the arguments positional when using the call operator '&'
Get-Help About_Parameters
https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_parameters
Regards,
Kvprasoon