Errors running remote powershell script with PsEcec.exe - powershell

I have a powershell script on a remote windows box that finds the folder pointed to by a junction. The contents of the script looks like this:
return fsutil reparsepoint query C:\foo\bar\junction_name | where-object { $_ -imatch 'Print Name:' } | foreach-object { $_ -replace 'Print Name\:\s*','' }
When I run this on the remote box, it executes as expected :)
However, when I try to run this remotely from my local machine:
C:\Users\foo>C:\pstools\PsExec.exe \\remote_server_name "powershell D:\bar\my_script.ps1"
I get errors:
PsExec could not start powershell D:\bar\my_script.ps1 on
remote_server_name: The filename, directory name, or volume label
syntax is incorrect.
Any ideas what this error is telling me (given that I can run the script directly on the remote box with no issues)?
Thx!

1- maybe you should avoid psexec and take advantage of powershell remoting
invoke-command -computername remote_server_name -scriptblock {. "D:\bar\my_script.ps1"}
2- if you want to keep psexec, look at the starting directory switch -w
PsExec.exe \\remote_server_name -w D:\bar "powershell -file my_script.ps1"

PS Remoting would be the best way to go here and I'd actually put up a good fight for opening up TCP/5985 on your machines. The minuscule security risk is, by far, worth the management benefits you'll get with it.
Worst case scenario use the WMI Win32_Process class. Something like this might work.
$wmiParams = #{
'ComputerName' = 'Somecomputer'
'Class' = 'Win32_Process'
'Name' = 'Create'
'Args' = 'fsutil reparsepoint query C:\foo\bar\junction_name > C:\temp.txt'
}
Invoke-WmiMethod #wmiParams
Get-Content \\somecomputer\c$\temp.txt | where-object { $_ -imatch 'Print Name:' } | foreach-object { $_ -replace 'Print Name\:\s*', '' }

I managed to get the following to work:
PsExec.exe \\remote_server_name powershell.exe D:\bar\my_script.ps1
However, the powershell session did not close as expected and remained in a hanging state after my script returned so calling it via cmd as detailed here seems to fix that:
PsExec.exe \\remote_server_name cmd /c "echo . | powershell.exe D:\bar\my_script.ps1"
Thanks for all of the suggestions...

Related

How to prevent multiple instances of the same PowerShell 7 script?

Context
On a build server, a PowerShell 7 script script.ps1 will be started and will be running in the background in the remote computer.
What I want
A safenet to ensure that at most 1 instance of the script.ps1 script is running at once on the build server or remote computer, at all times.
What I tried:
I tried meddling with PowerShell 7 background jobs (by executing the script.ps1 as a job inside a wrapper script wrapper.ps1), however that didn't solve the problem as jobs do not carry over (and can't be accessed) in other PowerShell sessions.
What I tried looks like this:
# inside wrapper.ps1
$running_jobs = $(Get-Job -State Running) | Where-Object {$_.Name -eq "ImportantJob"}
if ($running_jobs.count -eq 0) {
Start-Job .\script.ps1 -Name "ImportantJob" -ArgumentList #($some_variables)
} else {
Write-Warning "Could not start new job; Existing job detected must be terminated beforehand."
}
To reiterate, the problem with that is that $running_jobs only returns the jobs running in the current session, so this code only limits one job per session, allowing for multiple instances to be ran if multiple sessions were mistakenly opened.
What I also tried:
I tried to look into Get-CimInstance:
$processes = Get-CimInstance -ClassName Win32_Process | Where-Object {$_.Name -eq "pwsh.exe"}
While this does return the current running PowerShell instances, these elements carry no information on the script that is being executed, as shown after I run:
foreach ($p in $processes) {
$p | Format-List *
}
I'm therefore lost and I feel like I'm missing something.
I appreciate any help or suggestions.
I like to define a config path in the $env:ProgramData location using a CompanyName\ProjectName scheme so I can put "per system" configuration.
You could use a similar scheme with a defined location to store a lock file created when the script run and deleted at the end of it (as suggested already within the comments).
Then, it is up to you to add additional checks if needed (What happen if the script exit prematurely while the lock is still present ?)
Example
# Define default path (Not user specific)
$ConfigLocation = "$Env:ProgramData\CompanyName\ProjectName"
# Create path if it does not exist
New-Item -ItemType Directory -Path $ConfigLocation -EA 0 | Out-Null
$LockFilePath = "$ConfigLocation\Instance.Lock"
$Locked = $null -ne (Get-Item -Path $LockFilePath -EA 0)
if ($Locked) {Exit}
# Lock
New-Item -Path $LockFilePath
# Do stuff
# Remove lock
Remove-Item -Path $LockFilePath
Alternatively, on Windows, you could also use a scheduled task without a schedule and with the setting "If the task is already running, then the following rule applies: Do not start a new instance". From there, instead of calling the original script, you call a proxy script that just launch the scheduled task.

Open multiple remote sessions using 'mstsc' in powershell script

I am trying to write a powershell script that opens a remote desktop connection for each machine name saved in a text file. When I run the script, it only connects to the first machine in the list and outputs to the console: CMDKEY: Credential added successfully once (not once for each machine). mstcs seems to terminate the process after executing, and I'm not sure I'm adding credentials the right way. Can anyone point me in the right direction?
Here are some tests I've tried to figure out what's going on:
Print after mstsc. Doesn't print. Process seems to terminate after
mstcs is called. This seems to be the crux of the issue.
cmdkey /list shows all the credentials I have stored and their targets. The output does not include all the targets defined in the text file. Even if I comment out mstsc, then cmdkey /add:$MachineName /user:$User /pass:$Password only seems to execute for the first line, evidenced by the lack of more console outputs and cmdkey /list not yielding the expected targets. In addition, I have added a print statement after this cmdkey line and it prints for each line, so it doesn't terminate after running (which I already knew because mstcs executes after this line when it's not commented out).
# Read from file
$Lines = Get-Content -Path .\machines.txt | Out-String
# For each machine ...
foreach($Line in $Lines){
# Split line, save name and domain
$Tokens = $Line.Split(".")
$MachineName = $Tokens[0]
$Domain = $Tokens[1]
$User = "someDomain\someUsername"
$Password="somePassword"
# Switch username if someOtherDomain
if ($Domain -eq "someOtherDomain"){
$User = "someOtherDomain\someOtherUsername"
}
#set credentials and open connection
cmdkey /add:$MachineName /user:$User /pass:$Password
mstsc /v:$MachineName /console
}
EDIT: I have also tried replacing mstsc /v:$MachineName with Start-Process -FilePath "$env:windir\system32\mstsc.exe" -ArgumentList "/v:$MachineName" -Wait. The result is opening the session and then the script does not finish in the console but nothing additional happens.
This behavior is cause by your use of Out-String.
Get-Content outputs multiple strings, one per line in the file - but Out-String stitches them back together into a single multi-line string:
PS C:\> $machines = Get-Content machines.txt
PS C:\> $machines.GetType().Name # definitely an array
Object[]
PS C:\> $machines.Count # multiple strings in there
4
PS C:\> $machines = Get-Content machines.txt | Out-String
PS C:\> $machines.GetType().Name # now it's just a single string
String
So your foreach(){} loop only runs once, and the value of $MachineName is no longer the name of a single machine, but a multi-line string with all of them at once - which is probably why mstsc exits immediately :)
Remove |Out-String from the first line and your loop will work

Running .exe to get String output is not saving to string variable

Forgive me, I am new to PowerShell in general. I'm updating a build process that works on Linux (in bash) to one that will work on Windows in PowerShell.
My goal is to get the version of the game engine currently present on the build system. The default build location is well-known, so we try to execute it and get the version, like so:
$Version = & 'C:\Program Files\LOVE\love.exe' --version
When this executes, the $Version value is empty:
Write-Output $Version
[no output]
$Version -Eq $True
False
If I run my executable directly from the shell, I notice the line is not presented on a newline:
PS C:\Users\robbm\Myproject\Mygame> $Version = & 'C:\Program Files\LOVE\love.exe' --version
PS C:\Users\robbm\Myproject\Mygame> LOVE 11.3 (Mysterious Mysteries)
This makes me suspect there is some strange output behavior with the executable in the first place.
Is this a problem with LÖVE's --version output, or am I misunderstanding something about redirecting outputs in PowerShell? I've tried a few things to capture output, and $Version always seems to end up a nil value, such as:
$Version = & '\\build\love\love.exe' '--version' | Out-String
Write-Output $Version
$Version = (& '\\build\love\love.exe' '--version' | Out-String)
Write-Output $Version
Help is appreciated. As this works for other cmdlets, I'm inclined to believe it might be a function of LÖVE, but I'd appreciate thoughts as to how I could work with this anyway, or any method in which to capture the version it's clearly outputting to the screen when I execute it directly regardless.
EDIT:
LÖVE definitely does something different in regards to running on Windows. Looking at the version printing source, we are working with the aptly-named LOVE_LEGENDARY_CONSOLE_IO_HACK enabled on Windows, which appears to open a new console entirely, perhaps in cmd and write out there.
Doing the suggestions of commenters, I tried doing the .Exception.Message method, but there is none when called like so:
$Version = &('C:\Program Files\LOVE\love.exe' '--version').Exception.Message
So I'm still looking for ways to make this work within the confines of LÖVE hacking together some strange I/O stream.
EDIT2:
Another fun fact, redirection to a file similarly fails:
PS> (&'C:\Program Files\LOVE\love.exe' '--version') 2>&1 > .\love.txt
PS> LOVE 11.3 (Mysterious Mysteries)
PS> cat .\love.txt
[empty]
So this looks to be overly hacky on behalf of LÖVE, not an issue with PowerShell.
After reading your last edit, this probably won't help you, but may help others.
You could try to capture the output like this:
function runCmdAndCaptureOutput(
[Parameter(Mandatory=$true)]
[string] $cmd
) {
[string] $errOut
[string] $stdOut
# Deliberately dropped '$' from vars below.
Invoke-Expression $cmd -ErrorVariable errOut -OutVariable stdOut
if($LASTEXITCODE -ne 0) {
Write-Host -ForegroundColor Red "LASTEXITCODE: $LASTEXITCODE"
throw $LASTEXITCODE
}
return $stdOut
}
$exeCmd = "'C:\Program Files\LOVE\love.exe' --version"
$output = runCmdAndCaptureOutput -cmd $exeCmd
Write-Host $output

Return code and status from PowerShell command

I'm running the following command from within a Microsoft System Centre Orchestrator PowerShell activity:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
the command isn't doing what it's supposed to do, and I want to be able to return if the command was successful or not, and any error message if possible. Ignore the fact it's running in Orchestrator, as I'm more concerned about the PowerShell question. When I run the command from ISE it does what it's supposed to do, that's why I want to see what is returned from PowerShell.
Thanks.
It's hard to know what may be happening without more context. The following will record any errors encountered in an xml file that you can import later with import-clixml:
Install-WindowsFeature -ConfigurationFilePath C:\DeploymentConfigTemplate.xml -ComputerName ServerXYZ
IF (!($?)) {
$error[0] | export-clixml C:\myerror.xml
}
This solves my problem:
$Result = Install-WindowsFeature -Name SNMP-Service -IncludeAllSubFeature -IncludeManagementTools
Write-Host $Result.ExitCode

Powershell 2: Unable to suppress warning message

I am making a cmdlet call to 'set-distributiongroup' in powershell 2. I am simply setting the value of the parameter 'hiddenFromAddressListsEnabled' to a pre-defined boolean value.
However, no matter what I try, it displays a warning message if the boolean assignment is not actually changing the current value of 'hiddenFromAddressListsEnabled'.
Here is the main command I'm invoking:
set-DistributionGroup -identity TestGroup `
-hiddenFromAddressListsEnabled=$true
Let's semantically define what I have above as 'command'.
Now, I've tried adding several different variants, all with proper line-continuation and syntax. Here are those variants:
command > $null
command 2> $null
command -ErrorAction:silentlycontinue
command -ErrorVariable $throwAway
command -WarningAction:silentlycontinue
command -WarningVariable $throwAway
$var = command
Regardless of various combinations of one or more of the above, I still get a yellow WARNING: message spit to output. Specifically, this:
WARNING: The command completed successfully but no settings of
'<xxxxxx>/Users/TestGroup' have been modified.
Any suggestions on a key concept I'm not understanding? I want the command to not produce this output, and I want it to silently continue if this occurs.
Thanks!!
I've been trying to suppress the warning messages when stopping a service:
WARNING: Waiting for service 'Service Description' to finish stopping...
The following worked for me:
Stop-Service $svc.Name -WarningAction SilentlyContinue
If it's just a warning that cause problem why don't you set in your script $WarningPreference variable ?
PS C:\> $WarningPreference='silentlycontinue'
PS C:\> Write-Warning "coucou"
PS C:\> $WarningPreference='continue'
PS C:\> Write-Warning "coucou"
AVERTISSEMENT : coucou
just bumped this topic when searching on the issue,
my case PowerShell v2 , only after setting
$WarningPreference = "SilentlyContinue"
Write-Warning "blah" - returned me nothing... the parameter on the command didn't changed much too on my end.
You may be hitting this bug: http://connect.microsoft.com/PowerShell/feedback/details/541500/warning-verbose-and-debug-streams-do-not-respect-action-preferences-the-way-they-should
Anyway, your command should look like:
Set-DistributionGroup -Identity TestGroup -HiddenFromAddressListsEnabled $true
Your command is wrong. Which is the the reason why you get a yellow error message. The command should look like:
Set-DistributionGroup -Identity TestGroup -HiddenFromAddressListsEnabled $true
Or
Set-Distributionaliste -Identity TestGroup -HiddenFromAddressListsEnabled:$true
But not
Set-DistributionGroup -Identity TestGroup -HiddenFromAddressListsEnabled=$true
I was getting the same problem with the Exchange Management Console in 2010. Problem is the EMC runs on PowerShell 2.0, which as stated before has some bugs around warning preferences.
I found a cheeky workaround was to run my script in a vanilla PowerShell 4.0 shell, and then import the EMC cmdlets and start a new remote PS session like so...
. 'C:\Program Files\Microsoft\Exchange Server\V14\bin\RemoteExchange.ps1'
Connect-ExchangeServer -auto
...then, -WarningAction:SilentlyContinue suddenly starts behaving itself.
You should try
Set-DistributionGroup -Identity TestGroup -HiddenFromAddressListsEnabled $true -WarningAction silentlyContinue
I bumped into same issue, the following command seems to do the job (PS 3.0) :
Stop-Service<or whatever command> $svc.Name -WarningPreference SilentlyContinue
Don't know exactly what difference it makes with -WarningAction, though.
Hope this helps !
If you invoke powershell versión 2.0, you should use "-WarningAction silentlyContinue". I was having the same issue, but in the script, if I invoke, for example "C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -versión 2.0" , then you can use this parameter. I was trying on a scheduled task and using a ps1 script.
Try something like that:
PS C:\> {command} | Out-Null
For more Information: Technet: Out-Null