My problem is as follows:
I have two users on a windows server (Windows Server 2016). One is my regular user (with admin privileges) and the other a technical user.
I would like to probe for a given scheduled task on a remote machine (which is a Windows Server 2016 as well). To do so i use "Invoke-Command" and pass the session as follows:
Invoke-Command -Session $session -ScriptBlock { Get-ScheduledTask -TaskName <task_name>}
For my regular user this works (i am using localhost for testing, but also verified this against a different server)
For the technical user it does not. However, the command
Get-ScheduledTask -TaskName <task_name>
executed in the technical users' powershell WORKS.
Further (executed as the technical user), the following works as well
Invoke-Command -Session $session -ScriptBlock { Get-ChildItem C:\ }
So what i take from this is that for the technical user in general remoting works as well access to the scheduled tasks. However, somehow the combination does NOT.
The exception is get is
Access denied
+ CategoryInfo : PermissionDenied: (MSFT_ScheduledTask:Root/Microsoft/...T_ScheduledTask) [Get-ScheduledTask], CimException
+ FullyQualifiedErrorId : HRESULT 0x80041003,Get-ScheduledTask
+ PSComputerName : <hostname>
So obviously this is a permission issue. But i do not get what i need to change to fix it. I tried to compare the different permission between my regular user and the technical user, however I did not spot anything immediately obvious.
Does anyone know what needs to be changed?
"Enable Account" and "Remote Enable" permissions need to be be granted via WMI Control on Remote\Microsoft\TaskScheduler namespace.
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.
Assume I am running my machine as mydomain\myuser and I need to run some tools that require auth against someone elses domain. I do the following
runas /user:theirdomain\theiruser /netonly powershell.exe
Then in any powershell commands that I run in that powershell window I need to detect the theirdomain\theiruser that I ran with.
This was discussed here but for .net and there wasn't any solutions.
The implication was that you could run something remoted somewhere and be able to then ask that remote server what user they are using. I don't have any powershell remoting knowledge but lets assume that I have a Powershell running server somewhere that I could run a remote command against - could I use that to capture the NetOnly username?
in the meantime I think I will try to pass the username separately to the environment somehow but there must be a more elegant solution for this?
Thanks for any thoughts!
I recommend using the following:
Invoke-Command -ScriptBlock {Get-Process} -ComputerName $computerName -Credential (Get-Credential)
or you can use the following:
Enter-PSSession -Credential (Get-Credential) -ComputerName $computerName
If you prefer to run individual commands by hand, with those credentials:
Also, keep in mind Get-credential is not the only way to create a PSCredential, refer to Microsoft documentation for more examples.
I want to remotely trigger some commands with power shell on a Windows Server Core 2019.
I am using the following to enter the remote Session:
$Username = "x.x.x.x\Administrator"
$PasswordSS = ConvertTo-SecureString 'The-Password' -AsPlainText -Force
$Cred = New-Object System.management.Automation.PSCredential $Username,$PasswordSS
Enter-PSSession -ComputerName 'x.x.x.x' -Credential $cred
The connection then fails with the following message:
Enter-PSSession : Connecting to remote server x.x.x.x failed with the
following error message : Access is denied. For more information, see
the about_Remote_Troubleshooting Help topic. At line:4 char:2
+ Enter-PSSession -ComputerName 'x.x.x.x' -Credential $cred
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (x.x.x.x:String) [Enter-PSSession], PSRemotingTransportException
+ FullyQualifiedErrorId : CreateRemoteRunspaceFailed
The remote computer is on the local network and I can ping it.
The Administrator account is a local account on the remote computer.
The remote ip is on the trusted host list on the client.
PSRemoting was enabled on the remote computer.
What am I missing? Help would be appreciated.
This ought to be in a comment. Not enough reps
a. Reset PSSession configurations:
https://stackoverflow.com/a/22385798/10994804
b. Add -ComputerName 'x.x.x.x to trusted hosts.
Get-Item WSMan:\localhost\Client\TrustedHosts
Set-Item WSMan:\localhost\Client\TrustedHosts -Value x.x.x.x -Force
CONTEXT ANALYSIS:
You chose another Input Language than the Time And Currency Format language during the installation. However, your choice is ignored for the Welcome Screen.
At the end of the installation, at the first boot, when you are asked to enter a password for the Administrator account, you type a password with the Time And Currency Format language but you are not aware of this.
At every login, you will use the Time And Currency Format language, until you change this behavior, but you are still not aware of it.
That's why you cannot:
PSRemote
RDP
change the local Administrator's password with ALT + CTRL + SUPPR
SOLUTION:
To be able to RDP or PSRemote, you must set the password again but with PowerShell
Set-LocalUser -Name Administrator -Password (Read-Host -AsSecureString)
However, now you won't be able to login with the console anymore.
To fix this you must change the Welcome screen language.
Easy graphical solution for a few computers:
In the command prompt type intl.cpl to open the Region control panel.
Go to the Administrative tab.
Click on the Copy settings button.
Check the Welcome screen and system accounts check box.
Click OK
PowerShell solution for many computers:
1.Check your current substitutes
Get-ItemProperty -Path 'HKCU:\Keyboard Laytout\Substitutes'
The complete list of Keyboard Identifiers can be found on Microsoft Docs:
Keyboard Identifiers and Input Method Editors for Windows
2.Check current substitutes of the Default user account
Get-ItemProperty -Path 'Registry::HKEY_USERS\.DEFAULT\Keyboard Layout\Substitutes'
3. Add one or all missing substitutes to the Default user account
New-ItemProperty -Path 'Registry::HKEY_USERS\.DEFAULT\Keyboard Layout\Substitutes' -Name '00000809' -Value '00000040c' -PropertyType 'String'
4. Just for information have a look at the current values of the Preload key of the Default user account
Get-ItemProperty -Path 'Registry::HKEY_USERS\.DEFAULT\Keyboard Layout\Preload'
5. Configure value 1 of the Preload key with the substitute name which will be used first on the Welcome screen. (If you want, you can also remove other values from the Preload key or even reorder them...)
Set-ItemProperty -Path 'Registry::HKEY_USERS\.DEFAULT\Keyboard Layout\Preload' -Name 1 -Value '00000809'
How do I write a PowerShell 4.0 script to return the virtualization vs. physical status of Windows 2003, 2008 and 2012 servers? I want to know if these servers are virtual. I want to run the script on a central Windows server. The script will interact with remote servers. I know of a PowerShell command to run on individual servers that will tell me. But I want it to check many remote servers at a time. Here is my basic (not-yet-working) script for one server, but it isn't working:
$ComputerName = "greatServer"
$UserName = "greatServer\jdoe"
$Password = Get-Content C:\Users\jdoe\Documents\password.txt
$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $UserName , $Password
Get-WmiObject -Query "select * from win32_computersystem"
Here is the error that I get:
New-Object : Cannot find an overload for "PSCredential" and the argument count: "2".
At C:\Users\jdoe\Documents.pass.ps1:4 char:15 + $Credential = New-Object -TypeName System.Management.Automation.PSCredential -Ar ...+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo InvalidOperation: (:) [New-Object], MethodException + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand
The final query will have to be modified with a remove * and a comparison operation. The manufacturer in the results will indicate if it is a VMware, Hyper-V or physical server (e.g., Dell). I don't need help with modifying the final query. The select * will work assuming it can work on remote servers.
I really want help with the error that I am getting. I have written a working script, but it requires a user to enter the password manually. I just want help to make the script non-interactive (and suppress the prompt). I have been unsuccessful with passing a password argument to get around the interactive password entry requirement.
Does the file that holds that password need to be encrypted? Will this work on remote servers that are Windows Server 2003? I reviewed other scripts I found online. The "cat C:\password.txt" portion didn't seem to work for me. I tried the convertTo-string and securestring options. Both those caused errors too.
A better way is to store the credential securely. You will need to do this using the same user that will run the script (which means you might have to log on to that server as the serviceaccount which will run the script).
Anyway, from there you can do:
$cred = get-credential # Type in the greatserver credential
$cred | Export-Clixml -Path .\cred.xml
In your script you can now load the credential simply by running
$cred = Import-Clixml -Path .\cred.xml
This is the recommended way of persisting credentials to disk. Obviously, if you require a unique credential for each server you want to query this wouldn't work so well - you'd be better off using a domain account which is an administrator on all your servers (provided that your servers are actually joined to a domain).
Check out this script from the Technet Script repository, it pretty much does precisely what you need to do already.
The key to determining if a machine is virtual or not is to query the Win32_Bios and Win32_ComputerSystem classes to to check for Bios.Serial and ComputerSystem.Model and Manufacturer.
The way that the script works is to get the results of these queries then check to see if the Serial number field contains VMware, Xen, Microsoft or something else.
It's a lot of work to go creating a tool like this on your own since you'd need to research what to expect for various fields like bios, manufacturer and model and do some heavy lifting with scripts to get the right answer; I'd highly recommend you use this function instead.
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.