I'm having some trouble getting Clean Manager on Windows 10 to run remotely. I've seen a few different things were you can edit the registry and modify the /sageset or /sagerun to be specific things then run it remotely, but it seems no matter what I do the CleanMgr runs locally on my machine rather than running remotely.
I believe this is the closest I've gotten to get it to run remotely... It seems to still just run locally on my machine though.
Any ideas?
( All variables are set before this portion of the script, this is just a small portion of what's going on that I'm stuck on )
## Starts cleanmgr.exe
Function Start-CleanMGR {
Write-Host "Please provide your A-Account details to continue with this cleanup."
$creds = Get-Credential
Enter-PSSession -ComputerName $computername -Credential $creds
try {
$cleanmgr = Start-Process -Credential $creds -FilePath "C:\Windows\System32\cleanmgr.exe" -ArgumentList '/verylowdisk' -Wait -Verbose
if ($cleanmgr) {
Write-Host "Clean Manager ran successfully! " -NoNewline -ForegroundColor Green
Write-Host "[DONE]" -ForegroundColor Green -BackgroundColor Black
}
}
catch [System.Exception] {
Write-host "Cleanmgr is not installed! To use this portion of the script you must install the following windows features:" -NoNewline -ForegroundColor DarkGray
Write-host "[ERROR]" -ForegroundColor Red -BackgroundColor Black
}
} Start-CleanMGR
PowerShell always runs in the user context of the user who started the session. This is by design.
You can not run a GUI based application remotely using PowerShell. It is a Windows security boundary.
To run GUI apps, someone must be logged on, and you cannot use PowerShell to run code as the logged-on user.
You are also prompting for info, so, someone must be logged on.
If you are expecting a user to prived info, then you need to:
Create the script
Deploy the script to the user's machine or a files share from wich it
can be ran
Tell the user how to do it or create a batch file they would double
click to run the PowerShell script
Or
Set the script to run as a scheduled task at log on or at some point during the day, as the user credentials.
Variables have the scope and you cannot use local variables in a remote context unless they are scoped for that.
About Remote Variables
Using local variables
You can use local variables in remote commands, but the variable must
be defined in the local session.
Beginning in PowerShell 3.0, you can use the Using scope modifier to
identify a local variable in a remote command.
The syntax of Using is as follows:
$Using:<VariableName>
Still, the remote variable is not something you will do in your use case since you cannot do what you are after natively with PowerShell. You'll need a 3rdP tool like MS SysInternals PSExec to run code remotely as the logged-on user.
Using PsExec
Usage: psexec [\computer[,computer2[,...] | #file]][-u user [-p
psswd][-n s][-r servicename][-h][-l][-s|-e][-x][-i [session]][-c
executable [-f|-v]][-w directory][-d][-][-a n,n,...] cmd
[arguments]
-i Run the program so that it interacts with the desktop of the specified session on the remote system. If no session is specified the
process runs in the console session.
-u Specifies optional user name for login to remote computer.
I suggest that you use Invoke-Command
Function Start-CleanMGR ($computername, $creds) {
Invoke-Command -ComputerName $computername -Credential $creds -ScriptBlock {
try {
$cleanmgr = Start-Process -FilePath "C:\Windows\System32\cleanmgr.exe" -ArgumentList '/verylowdisk' -Wait -Verbose
if ($cleanmgr) {
return "Clean Manager ran successfully!"
}
}
catch [System.Exception] {
return "Cleanmgr is not installed! To use this portion of the script you must install the following windows features:"
}
}
}
Start-CleanMGR -computername "remotehost" -creds (Get-Credential)
As long as you execute cleanmgr.exe under a user account that has local admin rights, everything will work. Running cleanmgr.exe under the SYSTEM account, E.G. running from the Run Script Tool in SCCM/MECM will not work unless the script first opens a separate shell (DOS/PS) under a user that has local admin rights...even the cleanmgr.exe /verylowdisk will not run under the SYSTEM account.
Related
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 have an update script for running the Dell Command Update tool. In short dcu-cli.exe. The thing now is than when i run the same script code on the computer local then everything runs OK but when i run the exact same code in a script with invoke-command(and yes i have full admin rights) than the exitcode is 2 meaning An unknown application error has occurred instead of 0 (everything OK)
It is a very large script so i created a new one to debug this. This is the shorted code:
Invoke-Command -ComputerName "MyComputer" -ScriptBlock {
$ExitCode = 0
#Declare path and arguments
$DcuCliPath = 'C:\Program Files (x86)\Dell\CommandUpdate\dcu-cli.exe'
$DellCommand = "/applyUpdates -autoSuspendBitLocker=enable -outputLog=C:\Dell_Update.log"
#Verify Dell Command | Update exists
If (Test-Path -Path $DcuCliPath) {
$objWMI = Get-WmiObject Win32_ComputerSystem
Write-Host ("Dell Model [{0}]" -f $objWMI.Model.Trim())
$serviceName = "DellClientManagementService"
Write-Host ("Service [{0}] is currently [{1}]" -f $serviceName, (Get-Service $serviceName).Status)
If ((Get-Service $serviceName).Status -eq 'Stopped') {
Start-Service $serviceName
Write-Host "Service [$serviceName] started"
}
#Update the system with the latest drivers
Write-Host "Starting Dell Command | Update tool with arguments [$DellCommand] dcu-cli found at [$DcuCliPath]"
$ExitCode = (Start-Process -FilePath ($DcuCliPath) -ArgumentList ($DellCommand) -PassThru -Wait).ExitCode
Write-Host ("Dell Command | Update tool finished with ExitCode: [$ExitCode] current Win32 ExitCode: [$LastExitCode] Check log for more information: C:\Dell_Update.log")
}
}
When i remove the Invoke-Command -ComputerName "MyComputer" -ScriptBlock { and then copy + run the script local on the PC then the exitcode = 0
What i also noticed than when i run the command via 'Invoke-Command' then there is also no log file created as i passed along in the arguments... So my best guess is something is going wrong with local an remote paths?
So what am i missing? I'm guessing it is something simple but i spend several hours to get this running without any luck...
Try running it this way. You should be able to see any output or error messages. I typically add to the path first rather than using & or start-process.
invoke-command mycomputer {
$env:path += ';C:\Program Files (x86)\Dell\CommandUpdate';
dcu-cli /applyUpdates -autoSuspendBitLocker=enable -outputLog=C:\Dell_Update.log }
Using start-process inside invoke-command seems pretty challenging. I can't even see the output of findstr unless I save it to a file. And if I didn't wait the output would be truncated. By default start-process runs in the background and in another window. There's a -nonewwindow option too but it doesn't help with invoke-command.
invoke-command localhost { # elevated
start-process 'findstr' '/i word c:\users\joe\file1' -wait -RedirectStandardOutput c:\users\joe\out }
#js2010, thanks for your additional help. Unfortunately this didn't helped either.
So i did some more debugging and it turns out it was a bug in the dcu-cli version running on my test machine, DOH...!!
On the test machine version 3.1.1 was running and on another machine version 4.0 was running and that worked fine via remote Powershell. So i looked for the release notes, which i found here: https://www.dell.com/support/kbdoc/000177325/dell-command-update
And as you can see in version 3.1.3 there was this fix:
A problem was solved where dcu-cli.exe was not executed in an external interactive session of PowerShell.
So all I want to do is create a shortcut script that when clicked will restart the network adapter. The issue is that it needs to be ran on an account with basically no privileges so I need to have it run elevated and as a different user (admin account).
I cant quite figure out the right way to do this and its driving me nuts. This is what I have so far:
$username = "Domain\User"
$password = "Password"
$credentials = New-Object System.Management.Automation.PSCredential -ArgumentList #($username,(ConvertTo-SecureString -String $password -AsPlainText -Force))
start-process powershell -Credential ($credentials) -ArgumentList '-ExecutionPolicy unrestricted -noprofile -verb runas -inputformat text -command "{restart-netadapter -InterfaceDescription "Dell Wireless 1538 802.11 a/g/n Adapter" -Confirm:$false}"'
It will open a new powershell window but the command fails to run. It works fine on its own in an elevated powershell prompt. I found out at one point that even though I was calling the powershell using an admin account it wasn't an elevated powershell so I added the -verb runas but it still isn't working.
This really shouldn't be that hard, but I am not a powershell guru by any means. Any help is much appreciated. Thanks!
In my opinion, the best way to do this is to create a scheduled task that runs the script as a privileged account. Get rid of the embedded credentials altogether.
The limited account then only needs to be able to start the task.
Since the code to restart the adapter is a one-liner, you don't need even need to put it in a script file, so you don't need to worry about execution policy or anything.
This is the code that I used to get mine to work, I can't take credit for writing it because I found it from Here
# Get the ID and security principal of the current user account
$myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
$myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
# Get the security principal for the Administrator role
$adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
# Check to see if we are currently running "as Administrator"
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running "as Administrator" - so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "DarkBlue"
clear-host
}
else
{
# We are not running "as Administrator" - so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter
$newProcess.Arguments = $myInvocation.MyCommand.Definition;
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
exit
}
# Run your code that needs to be elevated here
Write-Host -NoNewLine "Press any key to continue..."
$null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
But this checks to see if it is elevated and then elevates it if it isn't.
This is something incredibly simple, but I just can't get anything to work. I want to run a block code in a powershell script under a specific user. The keyword is locally and I'm using powershell 2.0.
Invoke-Command seems to require a remote host? I run the following and the error message that I see seems to suggest as much:
$strScriptUser = "DOMAIN\USER"
$strPass = "PASSWERD"
$PSS = ConvertTo-SecureString $strPass -AsPlainText -Force
$cred = new-object system.management.automation.PSCredential $strScriptUser,$PSS
Invoke-Command -ComputerName "." -scriptblock {
write-output "HI!"
} -Credential $cred
Start-Job with -ScriptBlock isn't supported with powershell 2.0? I run the following and the error message that I see seems to suggest as much:
$strScriptUser = "DOMAIN\USER"
$strPass = "PASSWERD"
$PSS = ConvertTo-SecureString $strPass -AsPlainText -Force
$cred = new-object system.management.automation.PSCredential $strScriptUser,$PSS
Start-Job -ScriptBlock {
write-output "HI!"
} -Credential $cred
Am I doing something wrong, or is there an alternative way?
Added: Here is what I'm trying to do in the first place. I'm making a scheduled task that runs when a user logs into/unlocks a terminal that writes logon information to a file. The scheduled task runs as the local user in order to get at the username, profile, etc. information. The logon information is then written to a log file using a different user account, which is the only account that can modify the file. To deter access to the logon credentials in the script I convert the script to an EXE using PS2EXE.
Here is another way.
# Get the other user's credentials
$credential = Get-Credential
# Execute a scriptblock as another user
$commands = #'
$env:username
# ... more commands ...
'#
Start-Process -FilePath Powershell -LoadUserProfile -Credential $credential -ArgumentList '-Command', $commands
# Execute a file as another user
$script = '.\path\name.ps1'
Start-Process -FilePath Powershell -LoadUserProfile -Credential $credential -ArgumentList '-File', $script
With the -LoadUserProfile switch, this has the added benefit of creating the user's profile if it does not already exist.
Another approach is impersonation, it is good option if you are not willing to enable remoting.
Check this and this out.
You should just put your code between
Push-ImpersonationContext $credential
and
Pop-ImpersonationContext
It would help to see the error messages you're not showing us, but I think the answer to your question is to use PowerShell Remoting as you tried with Invoke-Command. The computer name . is fine as is localhost but you do have to have remoting enabled on your machine to do it.
To enable remoting, run Enable-PSRemoting within powershell, or run winrm quickconfig in a regular command prompt.
If you already have remoting enabled, then you might be trying to do the remoting with a non-administrative user. If that's the case, take a look at the output of Get-PSSessionConfiguration. You'll get a list of endpoints and the permissions that are applied.
The endpoint you're connecting to by default is called Microsoft.Powershell and you could change the permissions with Set-PSSessionConfiguration (be sure to use the -ShowSecurityDescriptorUI parameter unless you want to mess with SDDL).
But instead of doing that, there should already be a group given access called BUILTIN\Remote Management Users which you can add your limited user to.
If none of this helps, give more details and error messages.
Edit
After seeing the explanation of what you're ultimately trying to accomplish, I have another suggestion for you.
Your existing scheduled task writes the information to a known location.
A different scheduled task running under the privileged user account picks up that information and puts it into the file that the limited user cannot access.
Bonus: start the second task from the first task.
This could be a quick compromise to do what you want without remoting and without exposing the credentials of the privileged user.
Issues with the current approach:
The major problem I have with your original idea is that you're going to need to embed the credentials into the script, so the limited user will have access to the credentials of the account that can modify the file anyway.
Ideally:
You would have a web service that you could invoke with your limited-user powershell script in which you can only give it the login information and not get anything back. So you'd hit a URL and do a POST or whatever with the data that you want to log, but that user can't ever retrieve any info. It might be a bit beyond what you're willing to do for this.
I use the following command to run setup_server.exe on remote Windows box:
powershell -command "$encpass=convertto-securestring -asplaintext RPASSWORD -force;$cred = New-Object System.Management.Automation.PSCredential -ArgumentList RUSER,$encpass; invoke-command -computername RCOMPUTERNAME -scriptblock {setup_server.exe} -credential $cred;"
setup_server.exe's task is to create some configuration files and start my_server.exe (some daemon process), then it finishes. And I want my_server.exe to keep running after setup_server.exe is finished.
So when I do it via CMD on local box (i.e. just run setup_server.exe from CMD) it works, but when I do it via powershell on remote host it doesn't work. Namely the my_server.exe gets started, but right after setup_server.exe is closed the server also gets closed(killed).
So the question is following:
Which powershell flags/cmdlets should I use to make the described scenario to work as in local mode?
NOTE: I want synchronously get output of setup_server.exe, so running remote command with -AsJob flag, probably wouldn't work for me, though I even don't know if it will keep the server alive after setup_server.exe's end.
The way to keep the remote PowerShell session running after the command has finished is to use a PSSession e.g.:
$s = new-PSSession computername
Invoke-Command -session $s { ..script.. }
... do other stuff, remote powershell.exe continues to run
Remove-PSSession $s # when you're done with the remote session
Generally though exes should run independently from the app that launched them.
Why are you using Invoke-Command. If you want a persistent Session, use Enter-PSSession.
$s = New-PSSession -Computername "Computername";
Enter-PSSession -Session $s;
setup_server.exe
# Once you are finnished
Exit-PSSession
With 'Enter-PSSession' you are not just Invoking some Command on the Server, you are directly logged-in like you probably know from SSH.
If you want your powershell session to keep running because you are running an exe, try using the -InDisconnectedSession switch. From what I understand, it will run the executable on the remote machine in a session that isn't actually connected to your computer. In essence, your computer will not destroy the session, when it disconnects, allowing the exe to continue to run.
invoke-command -computername RCOMPUTERNAME -scriptblock {start-process setup_server.exe} -InDisconnectedSession
If you need to do this on multiple computers. Setup an array of all the computer names.
Note: I don't believe this works with sessions that are already created.
In order to keep a powershell code running on the session exit it should be a process. And the windows way to keep the process is running a .exe or a windows service.
To keep a Powershell shell open after executing a command, I use the -NoExit switch, e.g. this script starts a remote interactive PS session on servername with user administrator
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -NoExit
-Command "Enter-PSSession -ComputerName servername -Credential administrator"
http://powershell-guru.com/powershell-tip-13-prevent-powershell-from-exiting-once-script-finished/