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

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"

Related

Can PowerShell interact with RDP prompts?

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 %

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

net use * /delete /y doesn't resolve error "New-PSDrive : Multiple connections to a server ...."

This New-PSDrive command
New-PSDrive -Name Z -PSProvider FileSystem -Root \\$j\share -Credential $credentials -ErrorAction Stop
Causes the error
New-PSDrive : Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the
server or shared resource and try again
I have tried disconnecting all drives first, then creating new drive,
net use * /delete /y
New-PSDrive -Name Z -PSProvider FileSystem -Root \\$j\share -Credential $credentials -ErrorAction Stop
I get the output
You have these remote connections:
\\TSCLIENT\C
\\TSCLIENT\D
\\TSCLIENT\E
\\TSCLIENT\I
\\TSCLIENT\J
\\TSCLIENT\K
\\TSCLIENT\L
\\TSCLIENT\R
\\TSCLIENT\T
Continuing will cancel the connections.
The command completed successfully.
New-PSDrive : Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the
server or shared resource and try again
I also tried removing the Z drive first
Remove-PSDrive -name Z
New-PSDrive -Name Z -PSProvider FileSystem -Root \\$j\share -Credential $credentials -ErrorAction Stop
And get error
Remove-PSDrive : Cannot find drive. A drive with the name 'Z' does not exist.
...
New-PSDrive : Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed. Disconnect all previous connections to the
server or shared resource and try again
How to fix?
UPDATE
I even rebooted the machine and changed the drive name, but I still get the same error of "New-PSDrive: Multiple connections ......"
UPDATE 2
I have also tried using IP address instead of computer name, but that doesn't work either, http://support.microsoft.com/kb/938120
I found workaround to this problem that seem to always work. You need to change the computer name, but since this will also stop working eventually just as with server name and IP, you need the option to specify arbitrary number of new computer names resolving to the same computer.
The obvious choice is hosts file. You can add any number of aliases to the IP to it. Afterwards, use the alias that isn't already blocked.
==== EDIT ===
Here is the handy function:
<# Use unique hostname for this script by altering hosts table to handle situation with multiple different scripts
using this share with different creds which leads to following Windows error:
Multiple connections to a server or shared resource by the same user, using more than one user name, are not allowed.
Disconnect all previous connections to the server or shared resource and try again
#require -RunAsAdministrator
#require -module PsHosts
https://stackoverflow.com/a/29059100/82660
#>
function Set-UncHostnameAlias($UncPath) {
$remote_hostname = $UncPath -split '\\' | ? {$_} | select -First 1
$remote_alias = (Split-Path -Leaf $MyInvocation.ScriptName) -replace '.ps1$'
$hostEntry = Get-HostEntry $remote_alias* -ea 0 | ? { $_.Comment -eq $remote_hostname } | select -First 1
if (!$hostEntry) {
$remote_alias += (Get-HostEntry $remote_alias*).Count + 1
Write-Verbose "Adding alias $remote_alias => $remote_hostname"
$remote_ip = Test-Connection -ComputerName $remote_hostname -Count 1 | % IPV4Address | % IPAddressToString
Add-HostEntry -Name $remote_alias -Address $remote_ip -Force -Comment $remote_hostname | Out-Null
} else {
$remote_alias = $hostEntry.Name
Write-Verbose "Using $remote_hostname alias: $remote_alias"
}
$UncPath.Replace("\\$remote_hostname", "\\$remote_alias")
}
Do this on the start of the script:
$PathAlias = Set-UncHostnameAlias $Path
and used aliased path afterwards with the New-PSDrive. This works always, even if some other scripts on the same system use different credentials for the same server.
I was having the same issue with local scripts and found this to be a simple solution. The Get-CimInstance returns all of the mapped network connections, then just pass that to the net use /delete /y command.
$shareDrives = Get-CimInstance -ClassName Win32_NetworkConnection
if ($shareDrives -ne $null)
{
foreach ($shareDrive in $shareDrives)
{
Write-Host "`nRemoving mapped drive $($shareDrive.LocalName)"
net use $shareDrive.LocalName /delete /y
}
}
else
{
Write-Host "`nNo mapped drives to remove!"
}
Using the FQND worked for me..
How to find out FQDN??
ping -a -n 1
Pinging [This is the FQND!!!!] [192.168.0.1] with 32 bytes of
Reply from 192.168.0.1: bytes=32 time=1ms TTL=128
You need to use the FQDN instead of the NetBios name.
My script needs to be client computer independent since other members of my team might run it. This works for me. Not sure if the "Write-Host" is needed but it also doesn't get in the way. Also, there is some sort of error that doesn't affect using the drive again if it already exists.
if (Get-PSDrive DLL_NEW_TEMP -ErrorAction SilentlyContinue){Write-Host "DLL_NEW_TEMP Drive exists"}
else{
New-PSDrive -Name DLL_NEW_TEMP -PSProvider FileSystem -Root \\WTDHSxxxL32\d$\ServerDLLDev\New_DLL_temp_location -Credential $credential
}
if (Get-PSDrive DLL_WORKING -ErrorAction SilentlyContinue){Write-Host "DLL_WORKING Drive exists"}
else{
New-PSDrive -Name DLL_WORKING -PSProvider FileSystem -Root \\WTDHSxxx32\d$\ServerDLLDev -Credential $credential
}
The below information I found on here
This error message means that you already have a connection to that UNC path, whether it's defined on your computer or not. Windows only allows you to connect to a particular UNC path with one username, regardless of the number of connections to that UNC. If you use the same username for all connections to a UNC from your computer then you shouldn't run into this error.
My issue and its solution: I was connected to the shared drive and it was open in my session with my credentials and at the same I've tried to run New-PSDrive with other credential. After closing my session with the shared drive the command worked like a pro.
If you have to use a different username to connect, then one workaround is to connect to the UNC using an IP address or other alias so that it looks like it's a different path. This is also the workaround recommended by Microsoft:

Error: new-PSSession:One or more computer names is not valid

Back story:
I am working on a script that will be pointed at a Microsoft SCCM collection. This script is meant to query for all members in the collection and do something. So far I can restart specific services on all collection members, such as the SCCM service. The next bit I am working on is uninstalling a patch from each member in the collection.
Something like this works to restart the service on each collection member:
foreach ($member in get-cmdevice -collectionname "A collection name"){Get-Service -ComputerName $member.name -Name CcmExec | Restart-Service}
The script:
foreach ($member in get-cmdevice -collectionname "Some collection name")
{
$ession = new-PSSession -ComputerName $member
Invoke-Command -Session $ession {wusa.exe /uninstall /kb:######/quiet /log /norestart}
}
The resulting error:
new-PSSession:One or more computer names is not valid
Trying to emulate the 1st script, by only adding the invoke-command, results in the script trying to uninstall the update from the local computer.
Thoughts?
Got it. Had to add
.name
after $member down in the session. I was trying to pull the whole object / array in.

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!