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.
I am trying to get a list of installed programs on another computer in our domain, which requires my domain admin credentials.
$Name = Read-Host -Prompt 'Enter Computer Name'
Start-Process powershell -Credential "company\adminusername"
Get-WmiObject -ComputerName $Name -Class Win32Reg_AddRemovePrograms | Select DisplayName, Version | Sort-Object Name
I don't know if this is correct or not. But it tells me my username and password is incorrect. Which is false. I must be doing something wrong here.
When I write scripts. I literally just want to double click on them, and let them fly. I'm trying to avoid running a script just to open another script as admin.
To hold your credentials:
$cred = Get-Credential
Then use the $cred variable when you need to.
Ended up finding a great solution here that not only allows me to get credentials but store them permanently for importing later for use in other scripts as well, with option to encrypt the credential file if needed:
https://www.jaapbrasser.com/quickly-and-securely-storing-your-credentials-powershell/
I have 4-5 process (like java.exe, javaw.exe etc) having username "OWNER"(suppose). Below is the script that filters the java.exe process and kills it if it belongs to "OWNER". I need your help to modify this so that any process related to "OWNER" would be killed if found.
Just do it with Get-Process:
get-process -IncludeUserName | where username -like $username | stop-process
Basically your whole script can be replaced with this line
Get-Process with -IncludeUsername switch is only available in WMF 5.0.
WMI is the option here.
You could probably terminate the process just by checking the owner equals to the corresponding user.
Get-WmiObject -Class Win32_Process | Where-Object -FilterScript {
$_.GetOwner.User -eq "$Owner" } | Invoke-WmiMethod -Name Terminate
Edit: The above code is a one liner, you could save the out put of Get-WmiObject in a variable and for foreach through the collection to print the process id and call the terminate() method instead of using Invoke-WmiMethod.
Note:This code is not tested
I've written a small .ps1 script which automates simple commands in an attempt to fix any issues with the default printer on an end user's machine. How can I pass credentials in this script so that it always runs with elevated privileges?
net stop spooler
Remove-Item C:\Windows\System32\spool\PRINTERS\* -Force
net start spooler
This would be used across many different domains, but the admin username/password are consistent across all machines. I saw a similar question here, but the methods shown either involved saving the password as a .xml and then recalling it (which would make the password visible to the non admin), or the method simply wouldn't work for me; maybe it was my execution? Normally I wouldn't be comfortable scripting admin credentials into plain text, but the script isn't actually saved on the end users' machine, only executed in the background through RMM. Any help is appreciated.
I found the following article very helpful on how to secure credentials and use credentials stored on a text file:
http://www.adminarsenal.com/admin-arsenal-blog/secure-password-with-powershell-encrypting-credentials-part-1/
basically you would do the following:
<password> | ConverTo-SecureString -AsPlainText -Force | ConvertFrom-SecureString | Out-File <filename>.txt
You will then have a secure string file that would store your password for use in your script. You could also use the following command in order to perform the same action without ever having to use a plain text password:
(Get-Credentail).Password | ConvertFrom-SecureString | Out-File <filename>.txt
You can then add the following code to your script in order to use the password stored, I would recommend a shared location that only the admin has access on the workstation, and use it to run the mentioned script:
$credentials = Get-Content <filename>.txt | ConvertTo-SecureString
The final step is creating an object to use the credintials:
$adminCredentails = New-Object -TypeName System.Management.Automation.PSCredentail -ArgumentList <username>, (Get-Content <filename>.txt | ConvertTo-SecureString)
I leave to your imagination how you can then use the credentials in your script.
I have a Windows XP system and the user accounts are configured to have their passwords expire in 45 days option set. I am trying to figure out, either manually or via the use of a batch file, what the password expiry date is based on the current user logged in. I know that there are VBScript files that can accomplish this, but these pc's are configured to not execute VBScript files, therefore I need to either look this up manually or batch files.
Thanks!
If this is just on one computer, one user, and ran locally...
net user username | findstr "expires"
Multiple machines ran remotely for one user account... put all computer names or IP's in a text file (i.e. systems.txt)
psexec #systems.txt net user username | findstr "expires"
psexec is free from sysinternals
If you want to know the expiration date on all local users on multiple network computers you can use powershell and psexec (remote machines do not require powershell), like so...
$systems = get-content .\systems.txt;
foreach ($sys in $systems) {
foreach ($token in (Get-WmiObject Win32_UserAccount -ComputerName $sys -Filter "Domain='$sys'" | Select-Object -Property Name |ft -AutoSize -HideTableHeaders >> "$sys.txt")) { echo $token };
(cat "$sys.txt") -replace ' {2,}','' | ? {$_ -ne ''} | sc "$sys.txt"
foreach ($strUser in (get-content "$sys.txt")) {psexec \\$sys net user $strUser >> "$sys-accounts.txt"
}
}
you may need to tweak the script a little... hope this helps.