Thanks to Abbas, the following code enable us to call a cmd process and pass command to it using PowerShell script.
$psi = New-Object System.Diagnostics.ProcessStartInfo;
$psi.FileName = "cmd.exe"; #process file
$psi.UseShellExecute = $false; #start the process from it's own executable file
$psi.RedirectStandardInput = $true; #enable the process to read from standard input
$p = [System.Diagnostics.Process]::Start($psi);
Start-Sleep -s 2 #wait 2 seconds so that the process can be up and running
$p.StandardInput.WriteLine("dir"); #StandardInput property of the Process is a .NET StreamWriter object
Now, How can I use a CMD process that already exists.
In better words, I want to use the PID of a cmd.exe process that is running and pass the command to it.
Based on #Falcon's comment:
I want to be sure that the CMD is running as SYSTEM
I think the code should work, which checks for a command shell running as SYSTEM. It will return true for each matching shell that's running as SYSTEM, with title=TEST:
Get-CimInstance Win32_Process -Filter "name = 'cmd.exe'" | ForEach-Object {
if ((Get-Process -Id $_.ProcessId).MainWindowTitle -eq 'TEST') {
(Invoke-CimMethod -InputObject $_ -MethodName GetOwner).User -eq 'SYSTEM'
}
}
The above code needs running in an elevated shell
The code based on this article checks for the command prompt being elevated:
$p = Get-Process -Name cmd | where {$_.MainWindowTitle -eq 'TEST'} |
Select Name, #{Name="Elevated"; Expression={ if ($this.Name -notin #('Idle','System')) {-not $this.Path -and -not $this.Handle} } }
The code above needs running in a non-elevated PowerShell instance. It is testing for the absence of a path & handle - which the non-elevated shell can't see for an elevated command prompt. Change the eq 'TEST' condition to match your window.
Related
I just want to determine the working directory of a running process. In Linux you can use pwdx, but i cant find a similar tool in windows. It would be perfect to get a commandline solution.
I tried nearly every possible command for windows. wmic gwmic taskkill.. none of them is a solution for my problem.
The Get-Process command in PowerShell gives you the equivalent information (at least the path of the .exe)
>Get-Process ssms | Select -Expand Path | Split-path
C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\ManagementStudio
You could make this your own function, if you wanted:
Function pwdx{
param($Process)
Get-Process $Process | Select -Expand Path |Split-Path
}
C:\Users\FoxDeploy> pwdx notepad
C:\WINDOWS\system32
If you need to do this in the command line in Windows, you can find the process this way.
wmic process where "name like '%notepad%'" get ExecutablePath
ExecutablePath
C:\WINDOWS\system32\notepad.exe
If you want to receive the same information as the Process Explorer you can use a Windows Management Instrumentation query on the process.
Function Query-Process-WMI
{
param
(
[Parameter( Mandatory = $true )]
[String]
$processName
)
$process = Get-Process $processName
$processInfo = $process | ForEach-Object { Get-WmiObject Win32_Process -Filter "name = '$($_.MainModule.ModuleName)'" }
$processInfoCommandLine = $processInfo | ForEach-Object { $_.CommandLine }
return $processInfoCommandLine
}
I can determine running tasks with:
$TaskService = new-object -ComObject('Schedule.Service')
$TaskService.connect()
$TaskFolder = $TaskService.GetFolder('\')
$TaskFolder.gettasks(1) | ? {$_.state -eq 4}
Is there any way to identify the process IDs of those tasks if they start the same program (i.e. process name) as other existing processes?
My goal is a PowerShell script started from a scheduled task that can identify which scheduled task it is running under. I can easily determine the PoSh processID with $PID, but I don't know how to link that to a particular scheduled task.
Thanks.
This should work if you have this running in the script that is fired as an action. It will get the task path assuming it can be found by the RunningTasks COM object method
# Initiate a COM object and connect
$TaskService = New-Object -ComObject('Schedule.Service')
$TaskService.Connect()
# Query for currently running tasks
# 0 - the user is permitted to see.
# 1 - 0 + Hidden
$runningTasks = $TaskService.GetRunningTasks(0)
# Get the task associated to a certain PID
$runningTasks | Where-Object{$_.EnginePID -eq $PID} | Select-Object -ExpandProperty Path
Credit goes to eryksun for pointing out the method and linking to the ITaskService interface on MSDN
There is something to be said about the other suggestion of just telling your script what is running from via an extra parameter. That way you don't have to worry about a COM dependency.
param(
[string]$SuperImportantString,
[int]$NumberofBagels,
[string]$TaskInitiated
)
Set-Content -Path $file -Value "I'm running from $TaskInitiated"
Yes, this does make it more manual but you would have ultimate control over the text and such used and not have to worry about multiple tasks running from the same PID.
This seems like it tells me exactly which task started the script:
$EventFilter = #{
Logname = 'Microsoft-Windows-TaskScheduler/Operational'
ProviderName = "Microsoft-Windows-TaskScheduler"
Id = 129
Data = "$PID"
}
$ThisProcessEvent = Get-WinEvent -FilterHashtable $EventFilter -MaxEvents 1 -ErrorAction SilentlyContinue
$EventXML = [xml]$ThisProcessEvent.toxml()
$TaskFullName = $eventxml.event.eventdata.data | ? {$_.name -eq 'taskname'} |select -ExpandProperty "#text"
Let's say I have a script:
write-host "Message.Status: Test Message Status";
I managed to run it in a separate process by doing:
powershell.exe -Command
{ write-host "Message.Status: Test Message Status"; }
The problem is I want to pass parameters to the script so that I can achieve something like this:
write-host "I am in main process"
powershell.exe -Command -ArgumentList "I","am","here"
{
write-host "I am in another process"
write-host "Message.Status: $($one) $($two) $($three)";
}
However -ArgumentList doesn't work here
I get:
powershell.exe : -ArgumentList : The term '-ArgumentList' is not recognized as the name of a cmdlet, function, script file, or operable
I need to run some part of PowerShell script file in a different process and I cannot use another file due to the fact that PowerShell script is uploaded to external system.
The -Command parameter is expecting a scriptblock in which you can define your parameters using a Param() block. Then use the -args parameter to pass in the arguments. Your only mistake was to put the -args after -command before you defined the scriptblock.
So this is how it works:
write-host "I am in main process $($pid)"
powershell.exe -Command {
Param(
$one,
$two,
$three
)
write-host "I am in process $($pid)"
write-host "Message.Status: $($one) $($two) $($three)";
} -args "I", "am", "here" | Out-Null
Output:
I am in main process 17900
I am in process 10284
Message.Status: I am here
You can use the -File parameter and follow it by the path to script. Any unnamed arguments which follows will be passed as script parameters. Something like below should do
powershell -File "C:\ScriptFolder\ScriptwithParameters.ps1" "ParameterOneValu" "valuetwo"
Ok so if you need another process entirely but not another file then your best bet is probably .NET runspaces. Basically wrap your code in a scriptblock
$SB = {
*Your Code*
}
Then set up a runspace like below, making sure to use the "UseNewThread" as the thread option. Note that $arg is whatever your argument to be passed to the script is
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "UseNewThread"
$newRunspace.Open()
$psCmd = [PowerShell]::Create().AddScript($SB).AddArgument($arg)
$psCmd.Runspace = $newRunspace
$data = $psCmd.BeginInvoke()
You'll likely need to tweak this if you need to get any data back from the runspace once it is complete but there are a few ways to do that(leave a comment if you need assistance). If you need synchronous execution rather than async then change .BeginInvoke() to .Invoke()
So should get you started, But it will require a few moving parts.
First we define a new function:
function Run-InNewProcess{
param([String] $code)
$code = "function Run{ $code }; Run $args"
$encoded = [Convert]::ToBase64String( [Text.Encoding]::Unicode.GetBytes($code))
start-process PowerShell.exe -argumentlist '-noExit','-encodedCommand',$encoded
}
This function will be what starts the new process. It uses the start-process cmdlet, The -Argumentlist is our arguments applied to the powershell.exe You can remove -noExit to make the new process close on completion or add other powershell flags, and flags on Start-Process to get the windows and behaviours tweaked to your requirements.
Next we define our script block:
$script = {
[CmdletBinding()]
Param (
[Parameter(Position=0)]
[string]$Arg1,
[Parameter(Position=1)]
[string]$Arg2)
write-host "I am in another process"
write-host "Message.Status: $($Arg1) $($Arg2)";
}
Here we define some parameters in the opening part of the block, They have a position and name, so for example any argument in position 0 will be in the variable $arg1 The rest of the code in the block is all executed in the new process.
Now we have defined the script block and the function to run it in a new process, All we have to do is call it:
Run-InNewProcess $script -Arg1 '"WHAT WHAT"' -Arg2 '"In the But"'
Copy past this code all in to your ISE and you will see it in action.
Start-Job will create a process for its scriptblock, and it's straightforward to pass arguments to it.
Write-Host "Process count before starting job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
$job = Start-Job `
-ArgumentList "My argument!" `
-ScriptBlock {
param($arg)
Start-Sleep -Seconds 5;
Write-Host "All Done! Argument: $arg"
}
while ($job.State -ne "Completed")
{
Write-Host "Process count during job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
Start-Sleep -Seconds 1
}
Receive-Job $job -AutoRemoveJob -Wait
Write-Host "Process count after job: $((Get-Process |? { $_.ProcessName -imatch "powershell" }).Count)"
I'm running a powershell script, that when run from the ISE outputs one set of values but when the same task is run through task scheduler it seems to add a second value that doesn't display when run manually. The code that's being executed is as below:
import-module WebAdministration
$app_pool_name = <<app_pool_name_goes_here>>
$memused = ""
$cpuused = ""
$datetime = get-date -format s
$memused = Get-WmiObject Win32_process | where CommandLine -Match "$app_pool_name"
$id = dir IIS:\AppPools\$app_pool_name\WorkerProcesses\ | Select-Object -expand processId
$cpuUsed = Get-WmiObject Win32_PerfFormattedData_PerfProc_Process | where IDProcess -Match $id
Add-Content -path C:\Winsys\PSOutput\$app_pool_name-CPU-RAM_test.txt -value "$datetime,$($memUsed.workingsetsize),$($cpuUsed.PercentProcessorTime)"
When running the script manually the output returned is:
Date,Mem,CPU
2016-08-02T14:09:36,15062687744,0
2016-08-02T14:09:38,15062425600,0
When running the script through task scheduler the output returned is:
Date,Mem,CPU
2016-08-02T13:58:25,15065047040 624189440,0
2016-08-02T14:05:01,15061901312 624713728,0
The difference being the Mem, for some reason it's adding an extra value. Does anyone know why this is?
Turns out this was my own error, there are two app pools with very similar names, the -match was catching both. But it still didn't explain why it was only showing both in task scheduler and not ISE. Ah well, resolved now by adding a -and -notmatch "text" section.
E.g.
Get-WmiObject Win32_process | where {$_.CommandLine -Match "$app_pool_name" -and $_.CommandLine -notmatch "<<text in other command line>>"}
Add Comment
When I run a program on PowerShell it opens a new window and before I can see the output, the window closes. How do I make it so PowerShell keeps this window open?
Try doing:
start-process your.exe -NoNewWindow
Add a -Wait too if needed.
The OP seemed satisfied with the answer, but it doesn't keep the new window open after executing the program, which is what he seemed to be asking (and the answer I was looking for). So, after some more research, I came up with:
Start-Process cmd "/c `"your.exe & pause `""
I was solving a similar problem few weeks ago. If you don't want to use & (& '.\program.exe') then you can use start process and read the output by start process (where you read the output explicitly).
Just put this as separate PS1 file - for example (or to macro):
param (
$name,
$params
)
$process = New-Object System.Diagnostics.Process
$proInfo = New-Object System.Diagnostics.ProcessStartInfo
$proInfo.CreateNoWindow = $true
$proInfo.RedirectStandardOutput = $true
$proInfo.RedirectStandardError = $true
$proInfo.UseShellExecute = $false
$proInfo.FileName = $name
$proInfo.Arguments = $params
$process.StartInfo = $proInfo
#Register an Action for Error Output Data Received Event
Register-ObjectEvent -InputObject $process -EventName ErrorDataReceived -action {
foreach ($s in $EventArgs.data) { Write-Host $s -ForegroundColor Red }
} | Out-Null
#Register an Action for Standard Output Data Received Event
Register-ObjectEvent -InputObject $process -EventName OutputDataReceived -action {
foreach ($s in $EventArgs.data) { Write-Host $s -ForegroundColor Blue }
} | Out-Null
$process.Start() | Out-Null
$process.BeginOutputReadLine()
$process.BeginErrorReadLine()
$process.WaitForExit()
And then call it like:
.\startprocess.ps1 "c:\program.exe" "params"
You can also easily redirect output or implement some kind of timeout in case your application can freeze...
If the program is a batch file (.cmd or .bat extension) being launched with cmd /c foo.cmd command, simply change it to cmd /k foo.cmd and the program executes, but the prompt stays open.
If the program is not a batch file, wrap it in a batch file and add the pause command at the end of it. To wrap the program in a batch file, simply place the command in a text file and give it the .cmd extension. Then execute that instead of the exe.
With Startprocess and in the $arguments scriptblock, you can put a Read-Host
$arguments = {
"Get-process"
"Hello"
Read-Host "Wait for a key to be pressed"
}
Start-Process powershell -Verb runAs -ArgumentList $arguments
pwsh -noe -c "echo 1"