Launch Applications in WIndows using AppID and get the pid - windows

I'm trying to launch Windows applications using their AppID such as Microsoft.WindowsCalculator_8wekyb3d8bbwe!App which I get by calling Get-StartApps
Currently I can launch the applications but can't get the correct PID
cmd = exec.Command("powershell", "start", `shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App`)
err := cmd.Start()
fmt.Println(cmd.Process.Pid)
This returns the PID of powershell
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe start shell:AppsFolder\Microsoft.WindowsCalculator_8wekyb3d8bbwe!App
Is there a way to launch the application by the AppID and still get the correct PID?

tl;dr
// Make PowerShell not only launch Calculator, but also
// determine and output its PID, as described in the next section.
out, _ :=
exec.Command(
`powershell.exe`,
`-NoProfile`,
`-Command`,
`Start-Process -ErrorAction Stop calculator: ; (Get-Process Calculator | Where-Object SessionId -eq (Get-Process -ID $PID).SessionId).ID`,
).Output()
// Parse stdout output, which contains the PID, into an int
var pid int
fmt.Sscanf(string(out), "%d\n", &pid)
In principle, you can pass -PassThru to PowerShell's Start-Process (start) cmd, which returns a process-info object that has an .Id property containing the launched process' PID, and output the latter.
Unfortunately, with UWP / AppX applications specifically, such as Calculator, this does not work, which is a problem that exists in the underlying .NET APIs, up to at least .NET 6.0 - see GitHub issue #10996.
You can try the following workaround:
Launch the AppX application with Start-Process, which indirectly creates a process whose name is Calculator (Windows 10) / CalculatorApp (Windows 11).
You can identify this name yourself if you run (Get-Process *calc*).Name after launching Calculator. Get-Process *calc* | Select-Object Name, Path would show the executable path too, but note that this executable should be considered an implementation detail and can not be invoked directly.
Return the ID of that Calculator / CalculatorApp process. The fact that Calculator only ever creates one such process in a given user session actually makes identifying that process easy.
Note that this means that the PID of a preexisting Calculator process may be returned, which, however, is the correct one, because the transient process launched by Start-Process simply delegates creation of a new Calculator window to an existing process.
If you wanted to identify the newly created window, more work would be required: You'd have to enumerate the process' windows and identify the one with the highest z-order.
PowerShell code (note: in Windows 11, replace Calculator with CalculatorApp):
# Launch Calculator - which may reuse an existing instance and
# merely create a new *window* - and report the PID.
Start-Process -ErrorAction Stop calculator:
(Get-Process Calculator | Where-Object SessionId -eq (Get-Process -ID $PID).SessionId).ID
Note that I've used the URL scheme calculator: as a simpler way to launch Calculator.
Note:
The Where-Object SessionId -eq (Get-Process -ID $PID).SessionId guards against mistakenly considering potential Calculator processes created by other users in their own sessions (Get-Process returns all processes running on the local machine, across all user sessions). Filtering by .SessionID, i.e. by the active user session (window station), prevents this problem.
As a PowerShell CLI call:
powershell.exe -NoProfile -Command "Start-Process -ErrorAction Stop calculator: ; (Get-Process Calculator | Where-Object SessionId -eq (Get-Process -ID $PID).SessionId).ID"

Related

Using ssh to connect to Windows machine and start Docker Desktop in user's space [duplicate]

I've created a pssession on a remote computer and entered that possession. From within that session I use start-process to start notepad. I can confirm that notepad is running with the get-process command, and also with taskmgr in the remote computer. However, the GUI side of the process isn't showing. This is the sequence I've been using:
$server = New-PSSession -ComputerName myserver -Credential mycreds
Enter-PSSession $server
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized
The process is running, but while RDP'd to the box, notepad does not open. If I open notepad from the server, a new notepad process begins. I also tried by using the verb parameter like this:
[$server]: PS C:\>Start-Process notepad -Wait -WindowStyle Maximized -Verb Open
Same result tho... Process starts, but no notepad shows. I've tried this while remoted into the box (but issued from my local host) as well as before remoting into the server.
That is because your powershell session on the remote machine does not go to any visible desktop, but to an invisible system desktop. The receiving end of your powershell remote session is a Windows service. The process is started, but nor you nor anyone else can ever see it.
And if you think about it, since multiple users could RDP to the same machine, there is really no reason to assume a remote powershell session would end up showing on any of the users desktops. Actually, in almost all cases you wouldn't want it anyway.
psexec with the -i parameter is able to do what you want, but you have to specify which of the sessions (users) you want it to show up in.
I know this is old, but I came across it looking for the solution myself so I wanted to update it for future poor souls.
A native workaround for this problem is to use a scheduled task. That will use the active session
function Start-Process-Active
{
param
(
[System.Management.Automation.Runspaces.PSSession]$Session,
[string]$Executable,
[string]$Argument,
[string]$WorkingDirectory,
[string]$UserID
)
if (($Session -eq $null) -or ($Session.Availability -ne [System.Management.Automation.Runspaces.RunspaceAvailability]::Available))
{
$Session.Availability
throw [System.Exception] "Session is not availabile"
}
Invoke-Command -Session $Session -ArgumentList $Executable,$Argument,$WorkingDirectory,$UserID -ScriptBlock {
param($Executable, $Argument, $WorkingDirectory, $UserID)
$action = New-ScheduledTaskAction -Execute $Executable -Argument $Argument -WorkingDirectory $WorkingDirectory
$principal = New-ScheduledTaskPrincipal -userid $UserID
$task = New-ScheduledTask -Action $action -Principal $principal
$taskname = "_StartProcessActiveTask"
try
{
$registeredTask = Get-ScheduledTask $taskname -ErrorAction SilentlyContinue
}
catch
{
$registeredTask = $null
}
if ($registeredTask)
{
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
$registeredTask = Register-ScheduledTask $taskname -InputObject $task
Start-ScheduledTask -InputObject $registeredTask
Unregister-ScheduledTask -InputObject $registeredTask -Confirm:$false
}
}
When you use New-PSSession and then RDP into that same computer, you're actually using two separate and distinct user login sessions. Therefore, the Notepad.exe process you started in the PSSession isn't visible to your RDP session (except as another running process via Task Manager or get-process).
Once you've RDP'd into the server (after doing what you wrote in your post), start another Notepad instance from there. Then drop to PowerShell & run this: get-process -name notepad |select name,processid
Note that there are two instances, each in a different session.
Now open up Task Manager and look at the user sessions. Your RDP session will probably be listed as session 1.
Now quit Notepad and run get-process again. You'll see one instance, but for session 0. That's the one you created in your remote PSSession.
There are only 2 workarounds that I know of that can make this happen.
Create a task schedule as the logged in user, with no trigger and trigger it manually.
Create a service that starts the process with a duplicated token of the logged in user.
For the task schedule way I will say that new-scheduledtask is only available in Windows 8+. For windows 7 you need to connect to the Schedule Service to create the task like this (this example also starts the task at logon);
$sched = new-object -ComObject("Schedule.Service")
$sched.connect()
$schedpath = $sched.getFolder("\")
$domain = "myDomain"
$user="myuser"
$domuser= "${domain}\${user}"
$task = $sched.newTask(0) # 0 - reserved for future use
$task.RegistrationInfo.Description = "Start My Application"
$task.Settings.DisallowStartIfOnBatteries=$false
$task.Settings.ExecutionTimeLimit="PT0S" # there's no limit
$task.settings.priority=0 # highest
$task.Settings.IdleSettings.StopOnIdleEnd=$false
$task.settings.StopIfGoingOnBatteries=$false
$trigger=$task.Triggers.create(9) # 9 - at logon
$trigger.userid="$domuser" # at logon
$action=$task.actions.create(0) # 0 - execute a command
$action.path="C:\windows\system32\cmd.exe"
$action.arguments='/c "c:\program files\vendor\product\executable.exe"'
$action.WorkingDirectory="c:\program files\vendor\product\"
$task.principal.Id="Author"
$task.principal.UserId="$domuser"
$task.principal.LogonType=3 # 3 - run only when logged on
$task.principal.runlevel=1 # with elevated privs
# 6 - TASK_CREATE_OR_UPDATE
$schedpath.RegisterTaskDefinition("MyApplication",$viztask,6,$null,$null,$null)
Creating a service is way more complicated, so I'll only outline the calls needed to make it happen. The easy way is to use the invoke-asservice script on powershell gallery: https://www.powershellgallery.com/packages/InvokeAsSystem/1.0.0.0/Content/Invoke-AsService.ps1
Use WTSOpenServer and WTSEnumerateSessions to get the list of sessions on the machine. You also need to use WTSQuerySessionInformation on each session to get additional information like username. Remember to free your resources using WTSFreeMemory and WTSCloseServer You'll end up with some data which looks like this (this is from the qwinsta command);
SESSIONNAME USERNAME ID STATE
services 0 Disc
>rdp-tcp#2 mheath 1 Active
console 2 Conn
rdp-tcp 65536 Listen
Here's an SO post about getting this data; How do you retrieve a list of logged-in/connected users in .NET?
This is where you implement your logic to determine which session to target, do you want to display it on the Active desktop regardless of how it's being presented, over RDP or on the local console? And also what will you do if there is no one logged on? (I've setup auto logon and call a lock desktop command at logon so that a logged in user is available.)
You need to find the process id of a process that is running on the desktop as that user. You could go for explorer, but your machine might be Server Core, which explorer isn't running by default. Also not a good idea to target winlogon because it's running as system, or dwm as it's running as an unprivileged user.
The following commands need to run in a service as they require privileges that only system services have. Use OpenProcess to get the process handle, use OpenProcessToken to get the security token of the process, duplicate the token using DuplicateTokenEx then call ``CreateProcessAsUser``` and finally Close your handles.
The second half of this code is implemented in invoke-asservice powershell script.
You can also use the sysinternals tool psexec, I didn't list it as a 3rd way because it just automates the process of creating a service.

How to close other PowerShell windows from script

I have a PowerShell script which has the following steps:
Opens another PowerShell window
Navigates to an angular projects directory
Runs a command to serve the project
Is there a way that I can close all other running PowerShell windows, but keep the currently running script and it's newly created window open? Following the changes, I would like it to behave like this:
Close all other PowerShell windows
Opens another PowerShell window
Navigates to an angular projects directory
Runs a command to serve the project
You could use Get-Process to enumerate all running powershell processes, then filter out the current one by comparing with the value of $PID, before piping the rest to Stop-Process:
Get-Process powershell |? Id -ne $PID |Stop-Process -Force
You can include the child process by ID if necessary:
$childProcess = Start-Process powershell $childProcArgs -PassThru
Get-Process powershell |? Id -notin #($PID;$childProcess.Id) |Stop-Process -Force
... although I would suggest simply killing all other powershell instances first, and then launch the child process after
Is there a way that I can close all other running PowerShell windows,
but keep the currently running script and it's newly created window
open?
$Previous = Get-process -Name *Powershell*;
{YOUR SCRIPT}
Stop-process $previous

Check elevated process status?

I would like to find a way to find out if a process is running as elevated or not using Powershell.
Use Case: Being able to run control panel tasks with elevated privilage as local domain user e.g. Add or Remove programs.
Any help will be appreciated.
#Start add or remove as admin
start-process appwiz.cpl -verb runas
#Check if path exists. Answer is Yes, so process is NOT elevated
get-wmiobject -class win32_process | select-object -properties name, path
These are the two usual options:
Use the #requires -RunAsAdministrator line in your script (requires PowerShell 3.0 or later). If you use this line at the top of your script, it will throw a terminating error and won't execute if the current process isn't elevated.
Use code like the following to detect whether the current process is elevated:
$IsElevated = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)

Windows 10 applications how to get child process main window handle from parent process

Hi in Windows 10 some applications like calculator ,if I run calc.exe it spawns the calculator.exe and and immediately exit cal.exe.my requirement is get the main window handle of this child process (calculator.exe)
For getting the child process from parent process I tried following poweshell script.
function Find-ChildProcess {
param($ID=$PID)
Get-WmiObject -Class Win32_Process -Filter "ParentProcessID=$ID" |
Select-Object -Property ProcessName, ProcessID, CommandLine
}
This wokes with few applications like notepad but I am not getting child process for process like calculator and internet explorer..
Thank you

status of the process in powershell

A process in windows can be in any of the six states i.e, running, ready, blocked, suspend, new and exit. How to know the state a given process (name, ID) using powershell in windows.
In UNIX this information is stored in /proc/$processid/status file. Where is it found in windows or how to get this information in powershell.
"exit" status is signified by the presense of "exit code" property (natively returned by GetExitCodeProcess()). In PS, it is reflected by HasExited and ExitCode fields in Get-Process (alias ps).
ps | where {$_.Id -eq <PID>} | select HasExited,ExitCode
"running/wait/suspended" in Windows is a status of a thread rather than process ("suspend" being one of several Wait substates). I didn't find any info on getting thread information by PS's built-in means but we can call the corresponding .NET functionality:
$process=[System.Diagnostics.Process]::GetProcessById(<PID>)
$threads=$process.Threads
$threads | select Id,ThreadState,WaitReason
You are right, that's an interesting point. A way to find out about the state the process is the following way :
$ProcessActive = Get-Process outlook -ErrorAction SilentlyContinue
if($ProcessActive -eq $null)
{
Write-host "I am not running"
}
else
{
Write-host "I am running"
}
If outlook would not be a running process, it would not be listed but -ErrorAction SilentlyContinue will simply continue and return an I am not running
If it's running it will send you an I am running
I am not aware of other states of a process... at least not how to dertermine

Resources