Completely new to Powershell but not new to programming. Anyway, Im trying to edit this script that was already created and it is almost working. I dont understand how to make Powershell pass a user assigned variable into the filepath for my invoke-command (\MICROS\Res\pos\scripts\$ScriptName) . Any help you can offer will be greatly appricated.
#Gather user input for name of script
$ScriptName=Read-Host "Enter the name of the script that you want to run [Examples: HH_Off.bat]"
#Loop through store array
foreach ($CurrentStore in $store) { #START LOOP
#How to call current store Name: $CurrentStore.Name
#How to call current store IP: $CurrentStore.IP
#How to call current store City: $CurrentStore.City
if ($RunListArray -contains $CurrentStore.ID){ #If the current store is in the input list
Write-Host " *** "$CurrentStore.City"("$CurrentStore.Name")"
$NetworkDrivePath = "\\" + $CurrentStore.IP + "\D$"
#Create Network Drives
New-PSDrive -Name $CurrentStore.Name -PSProvider FileSystem -Root $NetworkDrivePath -credential $mycred
#Check if site is responding
If (Test-Path -Path $NetworkDrivePath)
{
Write-Host " *** *** NETWORK DRIVE FOUND!!! PROCEEDING..."
Invoke-Command {powershell.exe -noprofile -ExecutionPolicy ByPass \MICROS\Res\pos\scripts\$ScriptName} -computername $CurrentStore.IP -credential $mycred
#REMOVE PS DRIVE
Remove-PSDrive $CurrentStore.Name
}
Else {
Write-Host " *** *** NETWORK DRIVE NOT FOUND, SENDING ERROR EMAIL"
$Subject = "*Testing*Powershell Error Send Files Script" + $CurrentStore.Name + " (" + $CurrentStore.City + ")"
$Body = "Network path " + $NetworkDrivePath + " is not accessible."
SendErrorEmail $Subject $Body
}
} #END LOOP
}
The variable is defined in the local (calling) session and not in the remote (Invoke-Command) session. It's referred to as scope. You can reach into the calling scope with the variable modifier $using: like this
Invoke-Command {powershell.exe -noprofile -ExecutionPolicy ByPass \MICROS\Res\pos\scripts\$using:ScriptName} -computername $CurrentStore.IP -credential $mycred
Or as a named or unnamed parameter with -ArgumentList
#named parameter
Invoke-Command {Param($script)powershell.exe -noprofile -ExecutionPolicy ByPass \MICROS\Res\pos\scripts\$script} -computername $CurrentStore.IP -credential $mycred -ArgumentList $ScriptName
#unnamed parameter
Invoke-Command {powershell.exe -noprofile -ExecutionPolicy ByPass \MICROS\Res\pos\scripts\$args[0]} -computername $CurrentStore.IP -credential $mycred -ArgumentList $ScriptName
Formatted and using splatting for readability
$sb = {
Param($script)
powershell.exe -noprofile -ExecutionPolicy ByPass \MICROS\Res\pos\scripts\$script
}
$params = #{
ScriptBlock = $sb
ComputerName = $CurrentStore.IP
Credential = $mycred
ArgumentList = $ScriptName
}
Invoke-Command #params
Also you probably can and should avoid the extra call to powershell.exe.
Related
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.
I am writing a script in powershell where after login with User 1 on a system, it will switch to user 2 and then make a connection to database with this user. However, the dbinstance details, port No and Computer name to be passed in invoke command will be defined as a map before the 2nd invoke command i.e. when it will invoke the command to open powershell with 2nd user(db user). It is able to take userid in this case i.e. when to invoke the powershell connection with 2nd user, however it is not able to pass the values of dbinstance and port to next sqlcmd invoke. Below is the code for reference. In this code it works fine while getting $inputMap.UserNameP, however it fails in passing $inputMap.DBInstance,$inputMap.PortNo.
$UserName = 'User1'
$securekey = #'
securekey1
'# |ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $UserName, $securekey;
Invoke-Command -Credential $concreds -ComputerName 'abc.domainname'-Authentication Credssp -ScriptBlock {
function checkFaultHighUtilization() {
$local:ExecStdOperatorOut=Invoke-Command -ScriptBlock {
$inputMap=#{"UserNameP"="User2";"DBInstance"="databaseinstancename";"PortNo"="portnumber";};
$securekey1 = "securekey1"
$finalresult = #()
$securekey2 = $securekey1 | ConvertTo-SecureString -AsPlainText -Force;
$concreds=New-Object System.Management.Automation.PSCredential -ArgumentList $inputMap.UserNameP, $securekey2;
Invoke-Command -Credential $concreds -ComputerName 'computername' -Authentication Credssp -ScriptBlock {
$var1=Invoke-Sqlcmd -query "
Begin
select * from db
End" -ServerInstance "$inputMap.DBInstance,$inputMap.PortNo"
##if (($var1.count) -gt 0) {
foreach($row in $var1){
$finalresult+=$row.a+':'+$row.b+':'+$row.c
echo $finalresult
}
}
}
$local:ExecStdOperatorRet=if($local:ExecStdOperatorOut) {0} else {1}
return $local:ExecStdOperatorRet,$local:ExecStdOperatorOut;
};
$ESExecReturn,$ESExecOutput=checkFaultHighUtilization
$ESExecOutput=($ESExecOutput | Out-String).Trim();
Write-output "ESExecOutput:";
Write-output $ESExecOutput;
Write-output ":ESExecOutput";Write-output $("ESExecError:" + $Error + ":ESExecError");
Write-output $("ESExecReturn:" + $ESExecReturn + ":ESExecReturn");
}
$scriptBlockOne = {
$variableA = "Hello World"
return $variableA
}
$scriptBlockTwo = {
param (
$inputString
)
Write-host $inputString
}
$invokeCommandReturn = Invoke-Command -ScriptBlock $scriptBlockOne
Invoke-Command -ScriptBlock $scriptBlockTwo -ArgumentList $invokeCommandReturn
You're trying to use expressions such as $inputMap.DBInstance as-is inside an expandable string ("..."), which is syntactically not supported.
To use expressions, you must enclose them in $(...), the subexpression operator.
See this answer for a comprehensive discussion of string interpolation in PowerShell.
Therefore:
# ...
$var1 = Invoke-Sqlcmd -Query "
Begin
select * from db
End" -ServerInstance "$($inputMap.DBInstance),$($inputMap.PortNo)" # Note the $()
# ...
I've been trying for a couple of days now to multi-thread a WPF GUI which will run a PS3.0 script once the button has been clicked. I cannot use start-job as that I would have to track (multiple sessions at once), however, I would like to just run the script in a separate process of PS- as if I were to open multiple instances of the script from a shortcut. And be able to just have an open PS window which will track the progress within the script itself.
Expected results would be starting a script in powershell.exe session and passing 3 arguments - 2 strings and 1 boolean value. Which are provided by the user.
So in ISE:
C:\temp\test.ps1 -argumentlist $computername $username $citrixtest
Works fine.
I've spent a few hours scouring through the internet only to find a thread where a start-job was recommended or a way to use a background worker- this is not what I want from the script.
So I would guess the invocation from a button click would be something of the like (some of the things I have tried)
$ComputerName = "testtext1"
$UserName = "testtext2"
$CitrixTest = $True
$command = "c:\temp\test.ps1"
$arg = #{
Computername = "$computername";
Username = "$username";
CitrixTest = "$citrixtest"
}
#$WPFStartButton.Add_Click({
Start-Process powershell -ArgumentList "-noexit -command & {$command} -argumentlist $arg"
#})
Does not pass arguments to test.ps1- it is, however, getting to the "pause" - so the script successfully launches.
Where test.ps1 is
$ComputerName
$UserName
$CitrixTest
pause
Caller:
function Caller {
Param (
$ScriptPath = "c:\temp\test.ps1"
)
$Arguments = #()
$Arguments += "-computername $ComputerName"
$Arguments += "-UserName $UserName"
$Arguments += "-citrixtest $citrixtest"
$StartParams = #{
ArgumentList = "-File ""$ScriptPath""" + $Arguments
}
Start-Process powershell #StartParams
}
Caller
Does not start the script altogether- PS window just closes- possibly a path to .ps1 script not being found.
And a different approach which also nets in the script starts but not passing the arguments
$scriptFile = '"C:\temp\test.ps1"'
[string[]]$argumentList = "-file"
$argumentList += $scriptFile
$argumentlist += $computername
$argumentlist += $UserName
$argumentlist += $CitrixTest
$start_Process_info = New-Object System.Diagnostics.ProcessStartInfo
$start_Process_info.FileName = "$PSHOME\PowerShell.exe"
$start_Process_info.Arguments = $argumentList
$newProcess = New-Object System.Diagnostics.Process
$newProcess.StartInfo = $start_Process_info
$newProcess.Start() | Out-Null
Is there a way to make this work as I want it to? Or should I just dig deeper into runspaces and try with that?
#Bill_Stewart I just realized I did not put the param(args) in my script...
And that's why it would not pull those variables as I would like them to. I will have to check when I'm back in the office if it's just that what I was missing.
Checked on my laptop that's running PS 5.1 and this seems to be working as intended
$testarg = #(
'-File'
"C:\temp\test.ps1"
"$computername"
"$username"
"$citrixtest"
)
Start-Process powershell.exe -ArgumentList $testarg
Where test.ps1 is:
param(
$ComputerName,
$UserName,
$citrixtest
)
$ComputerName
$UserName
$CitrixTest
pause
We have a program that only updates when being run with the switch /t from an administrator account.
I came up with the CMD prompt version, but I'm new to powershell and having a hard time translating it to Powershell.
The CMD version is:
C:\Windows\System32\runas.exe /savecred /user:ourdomain\ouruseracct "C:\Program Files (x86)\ProjectMatrix\ProjectNotify\ProjectNotify.exe /t"
So far I got:
C:\Windows\System32\runas.exe /user:ourdomain\ouruseracct /savecred "powershell -c start-process -FilePath \"'C:\Program Files (x86)\ProjectMatrix\ProjectNotify\ProjectNotify.exe'\" -verb runAs"
Which runs powershell as admin and starts the program as admin but we need to pass the argument -t or /t to projectnotify.exe when running it.
I believe we need to make use of the -argumentlist but not sure how to word it.
I tried
$t = "-t"
Start-Process -FilePath "C:\Program Files (x86)\ProjectMatrix\ProjectNotify\projectnotify.exe" -ArgumentList $t -Verb runas
Which runs the program but not sure if that's how you pass the argument.
Extra work (troubleshooting):
$Cred = Get-Credential
$ProcInfo = New-Object -TypeName 'System.Diagnostics.ProcessStartInfo'
$ProcInfo.Domain = $Cred.GetNetworkCredential().Domain
$ProcInfo.UserName = $Cred.UserName
$ProcInfo.Password = $Cred.Password
$ProcInfo.FileName = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify\ProjectNotify.exe"
$ProcInfo.Arguments = '/t'
$ProcInfo.WorkingDirectory = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify"
$ProcInfo.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Normal
$ProcInfo.Verb = 'RunAs'
$ProcInfo.UseShellExecute = $true
[System.Diagnostics.Process]::Start($ProcInfo)
After some more thought, here's a simpler way (in a single command even):
Start-Job -Credential (Get-Credential) -ScriptBlock {
$Dir = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify"
$StartArgs = #{
'FilePath' = "$Dir\ProjectNotify.exe"
'ArgumentList' = '/t'
'Verb' = 'RunAs'
'WindowStyle' = 'Normal'
'WorkingDirectory' = $Dir
'PassThru' = $true
}
Start-Process #StartArgs
} | Wait-Job | Receive-Job
My previous answer is at the bottom of this post now.
References:
about_Splatting
Get-Credential
Start-Process
Start-Job
Extra reading:
Import-CliXml
Export-CliXml
Assuming an on-demand script, you should create a pscredential object if you want to natively run this from powershell:
Launch.cmd
SET "PS=%WINDIR%\System32\WindowsPowerShell\v1.0\powershell.exe"
SET "SCRIPT=%SYSTEMDRIVE%\Path\to\wrapper.ps1"
%PS% -NoProfile -NoLogo -ExecutionPolicy Bypass -File "%SCRIPT%"
wrapper.ps1
$Cred = Get-Credential
# To avoid prompting every time:
#
# if (-not (Test-Path -Path '.\mycred.xml')) {
# Get-Credential | Export-CliXml -Path '.\mycred.xml'
# }
# $Cred = Import-CliXml -Path '.\mycred.xml'
$StartArgs = #{
'FilePath' = "$PSHOME\powershell.exe"
'ArgumentList' = '-NoProfile', '-NoLogo', '-File', '.\runas.ps1'
'Credential' = $Cred
}
Start-Process #StartArgs
runas.ps1
$StartArgs = #{
'FilePath' = "${Env:ProgramFiles(x86)}\ProjectMatrix\ProjectNotify\ProjectNotify.exe"
'ArgumentList' = '/t'
'Verb' = 'RunAs'
}
Start-Process #StartArgs
I know the question asks for arguements, but if you don't them, this works:
Start cmd.exe -Verb RunAs
You can also run this using the 'Run' window or search box:
powershell -command start cmd.exe -verb runas
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.