I have a script that calls notepad on a remote computer with psexec. Is there a way I can get the Process ID after it is started?
Here is what I have:
$PCname = "MyPC"
$SessionID = "2"
$Program = "Notepad.exe"
$FilePath = "C:\temp\"
$FileName = "Test.txt"
set-alias psexec "C:\PsExec\psexec.exe"
&psexec -s -d -i $SessionID \\$PCname $Program $FilePath\$FileName
After running I get this in the output window that shows the Process ID:
Connecting to MyPC...Starting PSEXESVC service on MyPC...Connecting
with PsExec service on MyPC...Starting Notepad.exe on MyPC...
Notepad.exe started on MyPC with process ID 8352.
How can I grab the process ID?
You can use the Select-String cmdlet to grab the process ID using a regex:
&psexec -s -d -i $SessionID \\$PCname $Program $FilePath\$FileName |
Select-String 'process ID (\d+)' |
ForEach-Object {$_.Matches.Groups[1].Value}
$a = (gps -ComputerName PcName| where{ $_.ProcessName -eq "Notepad.exe"} | select Id)
$a.Id contains the wanted Id
Related
Hi I have a script to automate some tasks, running in powershell core v.7.+.
In one of these scripts when I run the command inside the ps1 file and the special characters returned is encoded and I can't decode to right format, below the command used to do this and the return:
// the variable $script is my command its a ask-cli command to work in alexa
$model = pwsh -Command $script
/*
* when I running the script from here the special characters returned is these:
* "nächste",
* "nächstgelegene"
*/
But when I run the same command directly in the terminal the strings returned are:
/*
* "nächste",
* "nächstgelegene"
*/
I would like to know how can I run the command inside the file without encode the return.
I already tried some things as:
$encoding = [System.Text.Encoding]::Unicode
$model = pwsh -Command $script
Write-Output $model
$model = $encoding.GetBytes($model)
$model = $encoding.GetString($model)
But don't work as expected, I don't know more how I can to this, if someone could help me with this I appreciate too much.
Try returning the string as bytes and then decode it from the place you are calling the function pwsh. This would preserve it from any changes. What you're doing is converting it into bytes after receiving it then returning it to string.
Below my script:
(Get-Content "$currentPath\skill-package\skill.json" -Raw | ConvertFrom-Json).manifest.publishingInformation.locales.PSObject.Properties | ForEach-Object {
$lang = $_.Name
Write-Output "Profile: $profile skill-id: $skillId language: $lang"
$script = "ask smapi get-interaction-model -l $lang -s $skillId -p $profile -g $env"
Write-Output 'Running script'
Write-Warning $script
# $encoding = [System.Text.Encoding]::ASCII
$model = pwsh -Command $script
Write-Output $model
$model = $model
| ConvertFrom-Json -Depth 100
| Select-Object * -ExcludeProperty version
| ConvertTo-Json -Depth 100
# out-file "$file$lang.json" -InputObject $model -Encoding ascii
Write-Output "New model saved locally $file$lang.json"
}
Write-Warning 'Finished!!!'
(Get-Content "$currentPath\skill-package\skill.json" -Raw | ConvertFrom-Json).manifest.publishingInformation.locales.PSObject.Properties | ForEach-Object {
$lang = $_.Name
Write-Output "Profile: $profile skill-id: $skillId language: $lang"
$script = "`$command = ask smapi get-interaction-model -l $lang -s $skillId -p $profile -g $env;`$command = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes(Invoke-Expression `$command));`$command"
Write-Output 'Running script'
Write-Warning $script
# $encoding = [System.Text.Encoding]::ASCII
$model = pwsh -Command $script
$model = Text.Encoding::Unicode.GetString([Convert]::FromBase64String($model))
Write-Output $model
$model = $model
| ConvertFrom-Json -Depth 100
| Select-Object * -ExcludeProperty version
| ConvertTo-Json -Depth 100
# out-file "$file$lang.json" -InputObject $model -Encoding ascii
Write-Output "New model saved locally $file$lang.json"
}
Write-Warning 'Finished!!!'
After many researches, I could find something more easiest to solve my problem.
Powershell has by default a different output encoder used in these cases, and the only thing I need to do it's change it.
I used the command:
$OutputEncoding = [console]::InputEncoding = [console]::OutputEncoding = New-Object System.Text.UTF8Encoding
I find this question explaining how this work and this help a lot, for more question please check this answer.
Thought I would share this quick function I made for myself, feel free to adapt it and improve it according to your needs.
Sometimes you want to run commands as the logged on user of a remote computer.
As you know, some commands show output for the user who runs it and if you run the same command with Invoke-Command, it won't return the user's information, but yours). Get-Printer is an example amongst many others.
There is no easy, quick way of running commands as the logged on user natively without any third-party apps like PsExec or others so I made this quick function that uses VBS, PS1 and Scheduled Task to make it happen.
It runs completly silently for the user (thanks to the VBS) and the output is shown in your console. Please note it assumes the remote computer has a C:\TEMP.
Created in a Windows 10, powershell v 5.1.17763.503 environement.
I don't pretend it's final and perfect, it's the simplest way I found to do what is needed and I just wanted to share it with you guys as it can be very useful!
Check the comments for explanation of the code and feel free to use it as you wish. Please share your version as I'm curious to see people improve it. A good idea would be to make it support multiple computers, but as I said it's a quick function I did I don't have too much time to put into refining it.
That being said, I had no problems using it multiple times as is :)
*Output returned is in form of a string, if you want to have a proper object, add '| ConvertFrom-String' and play with it :)
PLEASE NOTE: The surefire way of grabbing the username of who is currently logged on is via QWINSTA (since Win32_ComputerSystem - Username is only reliable if a user is logged on LOCALLY, it won't be right if a user is using RDP/RemoteDesktop). So this is what I used to grab the username, however, please note that in our french environement the name of the username property in QWINSTA is "UTILISATEUR",so you have to change that to your needs (english or other language) for it to work. If I remember correctly, it's "USERNAME" in english.
On this line:
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
See code in the answer below.
function RunAsUser {
Param ($ComputerName,$Scriptblock)
#Check that computer is reachable
Write-host "Checking that $ComputerName is online..."
if (!(Test-Connection $ComputerName -Count 1 -Quiet)) {
Write-Host "$ComputerName is offline" -ForegroundColor Red
break
}
#Check that PsRemoting works (test Invoke-Command and if it doesn't work, do 'Enable-PsRemoting' via WMI method).
#*You might have the adjust this one to suit your environement.
#Where I work, WMI is always working, so when PsRemoting isn't, I enable it via WMI first.
Write-host "Checking that PsRemoting is enabled on $ComputerName"
if (!(invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)) {
Invoke-WmiMethod -ComputerName $ComputerName -Path win32_process -Name create -ArgumentList "powershell.exe -command Enable-PSRemoting -SkipNetworkProfileCheck -Force" | Out-Null
do {
Start-Sleep -Milliseconds 200
} until (invoke-command $ComputerName { "test" } -ErrorAction SilentlyContinue)
}
#Check that a user is logged on the computer
Write-host "Checking that a user is logged on to $ComputerName..."
$LoggedOnUser = (qwinsta /SERVER:$ComputerName) -replace '\s{2,22}', ',' | ConvertFrom-Csv | Where-Object {$_ -like "*Acti*"} | Select-Object -ExpandProperty UTILISATEUR
if (!($LoggedOnUser) ) {
Write-Host "No user is logged on to $ComputerName" -ForegroundColor Red
break
}
#Creates a VBS file that will run the scriptblock completly silently (prevents the user from seeing a flashing powershell window)
#"
Dim wshell, PowerShellResult
set wshell = CreateObject("WScript.Shell")
Const WindowStyle = 0
Const WaitOnReturn = True
For Each strArg In WScript.Arguments
arg = arg & " " & strArg
Next 'strArg
PowerShellResult = wshell.run ("PowerShell " & arg & "; exit $LASTEXITCODE", WindowStyle, WaitOnReturn)
WScript.Quit(PowerShellResult)
"# | out-file "\\$ComputerName\C$\TEMP\RAU.vbs" -Encoding ascii -force
#Creates a script file from the specified '-Scriptblock' parameter which will be ran as the logged on user by the scheduled task created below.
#Adds 'Start-Transcript and Stop-Transcript' for logging the output.
$Scriptblock = "Start-Transcript C:\TEMP\RAU.log -force" + $Scriptblock + "Stop-Transcript"
$Scriptblock | out-file "\\$ComputerName\C$\TEMP\RAU.ps1" -Encoding utf8 -force
#On the remote computer, create a scheduled task that runs the .ps1 script silently in the user's context (with the help of the vbs)
Write-host "Running task on $ComputerName..."
Invoke-Command -ComputerName $ComputerName -ArgumentList $LoggedOnUser -ScriptBlock {
param($loggedOnUser)
$SchTaskParameters = #{
TaskName = "RAU"
Description = "-"
Action = (New-ScheduledTaskAction -Execute "wscript.exe" -Argument "C:\temp\RAU.vbs C:\temp\RAU.ps1")
Settings = (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -DontStopOnIdleEnd)
RunLevel = "Highest"
User = $LoggedOnUser
Force = $true
}
#Register and Start the task
Register-ScheduledTask #SchTaskParameters | Out-Null
Start-ScheduledTask -TaskName "RAU"
#Wait until the task finishes before continuing
do {
Write-host "Waiting for task to finish..."
$ScheduledTaskState = Get-ScheduledTask -TaskName "RAU" | Select-Object -ExpandProperty state
start-sleep 1
} until ( $ScheduledTaskState -eq "Ready" )
#Delete the task
Unregister-ScheduledTask -TaskName "RAU" -Confirm:$false
}
Write-host "Task completed on $ComputerName"
#Grab the output of the script from the transcript and remove the header (first 19) and footer (last 5)
$RawOutput = Get-Content "\\$ComputerName\C$\temp\RAU.log" | Select-Object -Skip 19
$FinalOutput = $RawOutput[0..($RawOutput.length-5)]
#Shows output
return $FinalOutput
#Delete the output file and script files
Remove-Item "\\$ComputerName\C$\temp\RAU.log" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.vbs" -force
Remove-Item "\\$ComputerName\C$\temp\RAU.ps1" -force
}
#____________________________________________________
#Example command
#Note: Sometimes Start-Transcript doesn't show the output for a certain command, so if you run into empty output, add: ' | out-host' or '| out-default' at the end of the command not showing output.
$Results = RunAsUser -ComputerName COMP123 -Scriptblock {
get-printer | Select-Object name,drivername,portname | Out-host
}
$Results
#If needed, you can turn the output (which is a string for the moment) to a proper powershell object with ' | ConvertFrom-String'
I am writing a function that will establish remote Wireshark packet capture sessions for VMs living on ESXi hosts.
This function will be given an array of vNIC objects, then based on information for each vNIC, will use plink and Wireshark locally to establish a remote live pcap feed using plink stdout > wireshark stdin.
I have decided to use cmd.exe to call plink/wireshark as the nuances of pipes in calling external commands in powershell have defeated me, happy to be shown another way to do this though.
I need to call the plink/wireshark command like this:
"C:\Program Files\PuTTY\plink.exe" -batch -l root -pw PASSWORD -P 22 remotehost.com pktcap-uw --switchport 1112222 -o - |
"C:\Program Files\Wireshark\wireshark.exe" -o "gui.window_title:VMName - Network adapter X - 1112222 - Portgroupname - VMHost" -k -i -
The code to produce the above command looks like this:
$command = "`"$($PlinkPath)`" -batch -l $($ESXiRootCredential.Username) -pw $($ESXiRootCredential.GetNetworkCredential().password) -P 22 $($currentHost.Name) pktcap-uw --switchport $($currentvNICSwitchPortInfo.portID) -o - `| `"$($WireSharkPath)`" -o `"gui.window_title:$($currentvNICSwitchPortInfo.VM.Name) - $($currentvNICSwitchPortInfo.Name) - $($currentvNICSwitchPortInfo.PortID) - $($currentvNICSwitchPortInfo.PortgroupName) - $($currentvNICSwitchPortInfo.VMHost.Name)`" -k -i - &"
This portion of the script works. The issue is with calling $command in cmd.exe using powershell but asynchronously so that if I pass in multiple vNICs, the for loop will spawn cmd.exe with $command then immediately move onto doing the same for the next vNIC, and so on.
I've tried a combination of several methods:
Invoke-Command
Invoke-Expression
& cmd.exe /c
etc.
#Requires -Modules VMware.VimAutomation.Common
function New-RemoteVMvNICPacketCapture {
[CmdletBinding()]
Param(
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[VMware.VimAutomation.Types.NetworkAdapter[]]$vNIC,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$true )]
[System.Management.Automation.PSCredential]$ESXiRootCredential,
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$false)]
[String]$WireSharkPath = 'C:\Program Files\Wireshark\wireshark.exe',
[ValidateNotNullOrEmpty()]
[Parameter(Mandatory=$false)]
[String]$PlinkPath = 'C:\Program Files\PuTTY\plink.exe'
)
Begin {
$ErrorActionPreference = 'Stop'
Write-Debug $MyInvocation.MyCommand.Name
# Import function needed to get switch port ID(s)
try {
. "$PSScriptRoot\Get-VMvNICSwitchPortInfo.ps1"
} catch {
Write-Error "Unable to import function Get-VMvNICSwitchPortInfo, exiting!"
break
}
}
Process {
try {
# Get unique list of ESXi hosts from supplied vNICs(s)
$uniqueESXiHosts = $vNIC |
Sort-Object -Property {$_.Parent.VMHost} |
Select-Object #{N="VMHost";E={$_.Parent.VMHost}} -Unique
foreach ($ESXiHost in $uniqueESXiHosts) {
# Get VMHost handle from current array index
$currentHost = $ESXiHost.VMHost
$sshService = $currentHost |
Get-VMHostService |
Where-Object {$_.Key -eq 'TSM-SSH'}
if ($sshService.Running -ne $true) {
$sshService | Start-VMHostService
}
if (-not (Test-NetConnection -Port 22 -ComputerName $currentHost)) {
Write-Error "Unable to connect to \"$currentHost\" on port 22. Skipping the following VMs: " + + ([String]::Join(', ',($vNIC | Where-Object {$_.Parent.VMHost -eq $currentHost} | Sort-Object -Property Parent).Parent.Name))
break
} else {
Write-Host "Able to connect to $currentHost on port 22"
}
foreach ($vnic in ($vNIC | Where-Object {$_.Parent.VMHost -eq $currentHost} | Sort-Object -Property Parent)) {
$currentvNICSwitchPortInfo = $vnic | Get-VMvNICSwitchPortInfo
# Create remote wireshark capture session
$command = "`"$($PlinkPath)`" -batch -l $($ESXiRootCredential.Username) -pw $($ESXiRootCredential.GetNetworkCredential().password) -P 22 $($currentHost.Name) pktcap-uw --switchport $($currentvNICSwitchPortInfo.portID) -o - `| `"$($WireSharkPath)`" -o `"gui.window_title:$($currentvNICSwitchPortInfo.VM.Name) - $($currentvNICSwitchPortInfo.Name) - $($currentvNICSwitchPortInfo.PortID) - $($currentvNICSwitchPortInfo.PortgroupName) - $($currentvNICSwitchPortInfo.VMHost.Name)`" -k -i - &"
Write-Host $command -ForegroundColor Yellow
Invoke-Command -ScriptBlock {& cmd.exe /c "$($command)"}
}
if ($sshService.Running -eq $false) {
$sshService | Stop-VMHostService -Confirm:$false
}
}
} catch [Exception] {
Write-Host $_.Exception.Message
throw "Unable to establish remote pcap"
}
}
End {}
}
This should spawn multiple instances of plink/wireshark one after another without having to wait. At the moment it spawns the first one and then waits for wireshark to close (and the associated cmd.exe process) before continuing.
I have the following tiny PowerShell script that's meant to kill some certain processes on a remote machine:
$destPS = "mywebserver1"
$brokerPIDs = Get-Process -ComputerName $destPS | ?{$_.processname -eq "uniRQBroker" -or $_.processname -eq "uniRTE"}
foreach ($process in $brokerPIDs){
$thisId = $process.ID
Write-Host "Killing PID $thisId"
Invoke-Command $destPS {Stop-Process $thisId}
}
However, I'm getting the following error:
Cannot bind argument to parameter 'Id' because it is null.
As far as I can see, the pipeline shouldn't be interrupted by anything, so I'm not quite sure where I'm going wrong.
The script block doesn't get the $thisId and that is set to null. So stop-process gives the error. You can pass the arguments to the script block like #Rynant mentions.
Since all you are doing is to get the processes and kill processes that match your requirement, move the commands into a script block and execute that scriptblock as whole using Invoke-Command on the remote box:
$script = {Get-Process -name uniRQBroker,uniRTE | stop-process -passthru | %{write-host killed pid $_.id}}
invoke-command -script $script -computer $destPS
You need to pass the variable thisId to the scriptblock as an argument (Invoke-Command executes the scriptblock in a separate temporary session when running against a remote computer, hence local variables are no longer in scope). Try it as:
Invoke-Command $destPS {Stop-Process $args} -ArgumentList $thisID
I am trying to output the following command to a text file in powershell, but I cannot seem to get it working:
ssh -v git#git.assembla.com | Out-File C:\output.txt
As stated in the post below with using native apps, you could try using Start-Process, e.g.
Start-Process ssh "-v git#git.assembla.com" -NoNewWindow -RedirectStandardOutput stdOut.log -RedirectStandardError stdErr.log; gc *.log; rm *.log
Working on the same problem I made a detail post on my blog How to SSH from Powershell Using Putty\Plink but the short version is this bit of code. But sure you try it after installing putty.
Function Invoke-SSHCommands {
Param($Hostname,$Username,$Password, $CommandArray, $PlinkAndPath, $ConnectOnceToAcceptHostKey = $true)
$Target = $Username + '#' + $Hostname
$plinkoptions = "-ssh $Target -pw $Password"
#Build ssh Commands
$remoteCommand = ""
$CommandArray | % {$remoteCommand += [string]::Format('{0}; ', $_) }
#plist prompts to accept client host key. This section will login and accept the host key then logout.
if($ConnectOnceToAcceptHostKey)
{
$PlinkCommand = [string]::Format('echo y | & "{0}" {1} exit', $PlinkAndPath, $plinkoptions )
#Write-Host $PlinkCommand
$msg = Invoke-Expression $PlinkCommand
}
#format plist command
$PlinkCommand = [string]::Format('& "{0}" {1} "{2}"', $PlinkAndPath, $plinkoptions , $remoteCommand)
#ready to run the following command
#Write-Host $PlinkCommand
$msg = Invoke-Expression $PlinkCommand
$msg
}
$PlinkAndPath = "C:\Program Files (x86)\PuTTY\plink.exe"
$Username = "remoteshell"
$Password = "pa$$w0rd"
$Hostname = "Linuxhost"
$Commands = #()
$Commands += "ls"
$Commands += "whoami"
Invoke-SSHCommands -User $Username -Hostname $Hostname -Password $Password -PlinkAndPath $PlinkAndPath -CommandArray $Commands