Can PowerShell interact with RDP prompts? - powershell

I'm currently writing a script to automate a number of checks, I have a number of clients which I want to automatically log into one of their servers or use an app hosted via RDweb.
Right now my script works fine, however, I'm only able to get to the point that it'll start to execute the RDP pointer, I'm wondering if there's a way to hit "connect":
The method I'm currently using to run this:
[System.Diagnostics.Process]::Start("c:\file\path\file.rdp")
Is there a better way to run the .RDP file which will also allow you to "Connect"? I've also attempted to tick the "don't ask me" again, the next day it'll still prompt me with this message.

A solution I've found to start an RDP session that seems to work quite good is the following:
function Connect-RDP {
param (
[Parameter(Mandatory=$true)]
$ComputerName,
[System.Management.Automation.Credential()]
$Credential
)
# take each computername and process it individually
$ComputerName | ForEach-Object {
# if the user has submitted a credential, store it
# safely using cmdkey.exe for the given connection
if ($PSBoundParameters.ContainsKey('Credential'))
{
# extract username and password from credential
$User = $Credential.UserName
$Password = $Credential.GetNetworkCredential().Password
# save information using cmdkey.exe
cmdkey.exe /generic:$_ /user:$User /pass:$Password
}
# initiate the RDP connection
# connection will automatically use cached credentials
# if there are no cached credentials, you will have to log on
# manually, so on first use, make sure you use -Credential to submit
# logon credential
mstsc.exe /v $_ /f
}
}
Then you call it with Connect-rdp -ComputerName myserver -Credential (Get-Credential ).
Maybe you can adjust your script to use this cmdlet instead of your file.rdp.
I found the solution here:
https://www.powershellmagazine.com/2014/04/18/automatic-remote-desktop-connection/
Another way you could try is this:
[void][System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
# Get the ID of the process
$WindowsHandle = Get-Process | Where-Object { $_.MainWindowTitle -Match 'Remote Desktop Connection' } | Select-Object -ExpandProperty Id
# Activate the window
$wshell = New-Object -ComObject wscript.shell;
$wshell.AppActivate($WindowsHandle) | Out-Null
# SendKey to connect
[System.Windows.Forms.SendKeys]::SendWait("%{c}")
%{c} stands for ALT+C
The modifier keys are:
Key | Code
-----------
SHIFT +
CTRL ^
ALT %

Related

Getting network printers from remote user, can variable be used in path?

I am getting the network printers from a remote user by getting the SID and querying the registry.
When I hard-code the SID in the path, it correctly displays the network printers for that user.
When I use a variable for the SID it fails with this error:
"Cannot find path '\HKEY_Users\Printers\Connections' because it does not exist. At line:18 char:7"
Here is the code:
# Get network connected printers from remote user by:
# 1) Connecting to computer name
# 2) Getting user and SID from AD
# 3) Query registry value
$MySAM = 'xyz1234'
$searcher = [ADSISearcher]"(&(objectClass=User)(objectCategory=person)(sAMAccountName=$MySAM))"
$user = $searcher.FindOne().GetDirectoryEntry()
# get binary SID from AD account
$binarySID = $user.ObjectSid.Value
# convert to string SID
$stringSID = (New-Object System.Security.Principal.SecurityIdentifier($binarySID,0)).Value
# computer name
$computer = 'xyz123456'
$Session = New-PSSession $computer -ErrorAction Stop # try to remote connect
Invoke-Command -Session $Session -ScriptBlock {
# works when SID hard-coded into path
# Get-ChildItem "Registry::\HKEY_Users\S-1-5-21-2009805145-1601463483-1839490880-13975\Printers\Connections"
# fails when using SID variable
Get-ChildItem "Registry::\HKEY_Users\$stringSID\Printers\Connections"
}
Remove-PSSession -Session $Session
In your question, you set $stringSID before connecting to the remote session. This means $stringSID will be blank on the remote machine since it's a (mostly) separate environment. However, you can specify that you want variables from your local session with the $Using:var scope. A good example is to try both out:
$Hello = 'Hello'
$World = 'World'
Invoke-Command -ComputerName RemotePC {
Write-Output $Hello $using:World
}
World
Another issue you may have have is the registry hive for other users may not get loaded in a remote session. We can fix it manually by LOADing it with the old REG tool. As a bonus, you can get the currently logged in user on the remote machine, and use their username instead of SID:
# example to get the currently logged-on user: (in DOMAIN\Username format).
# note: returns nothing if multiple users logged in (through RDP etc)
$LoggedOnUser = (Get-CimInstance -class Win32_ComputerSystem).username
# split the domain section of the username off:
$Username = ($LoggedOnUser -split '\\')[1]
# load the user's registry hive to an easy to find path:
REG LOAD "HKEY_USERS\$Username" "C:\Users\$Username\NTUSER.DAT"
# Now you can search their registry
Get-ChildItem "Registry::HKEY_USERS\$Username\Printers\Connections"

Setting autologon to a local user after joining to a domain

I'm building an application to assist in re-configuring devices on our network. I've got the entire script working except for setting the device to auto logon. Nothing I am trying seems to work. Now, the use is a little different, as the device will be joined to the domain, but logged on with a local user account. I'm trying to use the Sysinternals Autologon utility, but it is not working.
Here is the relevant code for logging on:
function Set-Autologon {
param (
[Parameter()][string]$domain
)
$exePath = "$scriptPath\$autologon"
$user = 'aloha'
$logonPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
$defaultdomain = "DefaultDomainName"
$alohass = '<very long string>' | ConvertTo-SecureString -Key $key
$alohaptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($alohass)
$aloharesult = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($alohaptr)
LogInfo "Setting auto logon for $aloha user."
Start-Process -FilePath $exePath -ArgumentList "/accepteula", $user, $domain, $aloharesult -Wait -Verbose
}
The domain is being passed in as that is coming from a CSV file. The $key is set somewhere else in the script. I've tried with the /accepteula in both the front and the back.
What am I missing here?
Thanks
This should work with "/accepteula" as the last argument but, you can create a DWORD registry key in HKCU\Software\Sysinternals\AutoLogon for EulaAccept=1 before running the exe. You could also try starting the autologon.exe with the /accepteula flag and then immediately killing the process before running with the username/credentials arguments.

Powershell Suppress Certificate Notification

I am writing a script to connect from a Windows 10 Client to a Terminal Server with the RDP-Protocoll.
The thought behind is: On these ThinClients we have about 20 RDP-Files. With about 10 of them, the password needs to be safed.
So it is quite a lot of work if you always have to save the password on every new ThinClient.
But I thought I could solve this problem with a powershell script. I just have to open the connection 1 time successfully and save the credentials and further on the credentials are saved.
I will show my code first:
$Server = "xx.yy.zz.xx"
$User = "DOMAIN\User"
$password = "password"
cmdkey /generic:"$Server" /user:"$User" /pass:"$password"
mstsc /v:"$Server"
This works so far.
But I always get the this Notification:
This is a Symbol-Picture from the Internet, as my Notification is in German. It is exactly the same, just easier to understand.
Even if I install the certificate, the notification keeps popping up.
How can I check the field with Powershell, where it says Don't ask me again for connections to this computer?
Ok i found a solution!
There is a registry-Key generated when you Tick "Dont ask me again..."
Now i just added the necessary Registry Keys with my Powershell Script..
function Test-RegistryValue {
param (
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Path,
[parameter(Mandatory=$true)]
[ValidateNotNullOrEmpty()]$Value
)
try{
Get-ItemProperty -Path $Path | Select-Object -ExpandProperty $Value -ErrorAction Stop | Out-Null
return $true
}
catch{
return $false
}
}
#Certificate warning turn off
$exists = Test-RegistryValue -Path 'HKCU:\Software\Microsoft\Terminal Server Client' -Value 'AuthenticationLevelOverride'
if($exists -eq $False){
reg add "HKEY_CURRENT_USER\Software\Microsoft\Terminal Server Client" /v "AuthenticationLevelOverride" /t "REG_DWORD" /d 0 /f
}
Like this it works without this certificate notification!

Running Command as Administrator from a SYSTEM Process

So I need to clear a user's run dialog history which I can do perfectly fine with "reg delete HKEY_CURRENT_USER\Software\Windows etc..." from an elevated powershell window on the logged in user's machine, but what I'm looking to do is that same command but from a SYSTEM powershell process. I have already used psexec to create a powershell window which runs as SYSTEM, but because you can't just use HKEY_CURRENT_USER as SYSTEM with the same results, I am finding it quite difficult. If I could just run that command but as username\Administrator then I wouldn't have this problem.
Also to note, if I can somehow grab the username of the logged on user (from SYSTEM still) in one line in plain text (with no other output in sight), then I can store the username in a variable and convert that to an SID and use HKEY_USERS instead.
P.S. Don't ask why I'm running powershell as SYSTEM, I know what I'm doing :D
you can use get-process under the system context powershell and filter where explorer.exe process is running, get the account it is running under then use to convert to SID and go through the registry.
something like this assuming only 1 explorer.exe process is running which is the norm on windows client OS.
$proc = Get-CimInstance Win32_Process -Filter "name = 'explorer.exe'"
$owner = Invoke-CimMethod -InputObject $proc -MethodName GetOwner
$username = $owner.user
$username will contain the user, $owner will also contain domain and a few other things.
to convert to sid
$objUser = New-Object System.Security.Principal.NTAccount($owner.Domain, $owner.User)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$strSID.Value

OpenRemoteBaseKey() credentials

I'm attempting to use powershell to access a remote registry like so:
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $server)
$key = $reg.OpenSubkey($subkeyPath)
Depending on some factors that I'm not yet able to determine I either get
Exception calling "OpenSubKey" with "1" argument(s): "Requested registry access is not allowed."
Or
System.UnauthorizedAccessException: Attempted to perform an unauthorized operation.
at Microsoft.Win32.RegistryKey.Win32ErrorStatic(Int32 errorCode, String str)
at Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(RegistryHive hKey, String machineName)
It seems pretty clear that this is because the user I'm running the powershell script as doesn't have the appropriate credentials to access the remote registry. I'd like to be able to supply a set of credentials to use for the remote registry access, but I can find no documentation anywhere of a way to do this. I'm also not clear on exactly where to specify which users are allowed to access the registry remotely.
Just thought I'd add my answer to anyone with this problem as well. It seems there is no way to add Credentials using RemoteRegistry. You can however use WMI to query a remote registry using alternative credentials as follows:
$reg = Get-WmiObject -List -Namespace root\default -ComputerName RemotePC -Credential "Domain\User" | Where-Object {$_.Name -eq "StdRegProv"}
From here you can call standard Registry methods. The below example will return the operating system.
$HKLM = 2147483650
$reg.GetStringValue($HKLM,"SOFTWARE\Microsoft\Windows NT\CurrentVersion","ProductName").sValue
Hope this helps someone :)
Are you running remote registry service? It is disabled by default and that must be causing the issue. Check the status of this service on all remote machines you are trying to access.
I couldn't comment directly on bentaylr's entry above, but I've taken what he contributed and added PSCredentials creation (figured out from here) to allow you to hard code credentials into the script.
Peace of mind disclaimer: Be careful when using plaintext credentials in a script. In my case, I'm using generic credentials on machines I'm launching. Depending on your case, you might consider creating an encrypted credential file to store the password in (see link above).
The credentials you use would need to be able to access the registry if you were logged into that user on the machine you are targeting.
$user = "Domain\Username"
$pass = ConvertTo-SecureString "Password" -AsPlainText -Force
$cred = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $user,$pass
$reg = Get-WmiObject -List -Namespace root\default -ComputerName $server -Credential $cred | Where-Object {$_.Name -eq "StdRegProv"}
$HKLM = 2147483650
$value = $reg.GetStringValue($HKLM,"Software\Microsoft\.NetFramework","InstallRoot").sValue
$key.OpenSubKey($subkeyName) opens the subkey in write protected mode,
$key.OpenSubKey($subkeyName,$true) opens it in writable mode
Therefore after $key.OpenSubKey($subkeyName,$true) you should be able to create a new subkey or value
If you try the same thing after $key.OpenSubKey($subkeyName) you will get "UnauthorizedAccessException"
PS C:\>$regKey.OpenSubKey
OverloadDefinitions
Microsoft.Win32.RegistryKey OpenSubKey(string name, **bool Writable**)
try
PS C:\>$key.OpenSubKey($subkeyName,**$true**)
http://msdn.microsoft.com/en-us/library/xthy8s8d%28v=vs.110%29.aspx
Came looking for the answer to your question, but in a little googling this morning I noticed that the first parameter is a type rather than a String... hope this helps:
$machine = "<Machine Name Goes Here>"
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regkey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($type,$machine)
$subkey = $regKey.OpenSubKey($key)
foreach ($sub in $regKey.GetSubKeyNames()){$sub}
I wanted to first thank all for answers above really helpful, wanted to add that you can use Get-Credential command to collect credentials without having to hard code it in your script. I have written using the above suggestions into my script the following code and query:
$userCredentials = Get-Credential -Credential <domain\username>
$objReg = Get-WmiObject -List -Namespace root\default -ComputerName $server -Credential $userCredentials | Where-Object{$_.Name -eq "StdRegProv"}
$subKeyNames = $objReg.EnumKey($HKLM,"SOFTWARE\Microsoft\Updates\Microsoft .Net Framework 4.5.1").sNames
The above code returns all sub key names in the specified key so that I can determine installed updates other than OS which have been applied to a server. If you want to determine all collection possibilities with the $objReg variable then run:
$objReg | Get-Member
You will see a list of all possible queries which can be performed against the registry. Hope this helps!