Check if a Registry Path Exists in Remote Machine - powershell

I have used Power Shell to check if a path exists using this command . powershell test-path "HKCU:\Software\Microsoft\Windows" now how can the same be extended to remote machine. What is the syntax if i want to test a registry path in Remote machine, i tried powershell test-path "\\machinename\HKCU:\Software\Microsoft\Windows" and its not working. Suggest some way to test it.

You can access it as outlined here: http://powershell.com/cs/blogs/tips/archive/2011/02/15/accessing-registry-remote.aspx
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', 'server123')
$key = $reg.OpenSubKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall')
$key.GetSubKeyNames() | ForEach-Object {
$subkey = $key.OpenSubKey($_)
$i = #{}
$i.Name = $subkey.GetValue('DisplayName')
$i.Version = $subkey.GetValue('DisplayVersion')
New-Object PSObject -Property $i
$subkey.Close()
}
$key.Close()
$reg.Close()
An alternative is to enable PSRemoting and use invoke-command on the remote machine and effectively run the same command as what you would run on the local box.

You cannot connect to the current user hive of a remote computer. Here's an example of using the Remote Regitry module to check if a remote key exists in the hklm hive of a remote server. The module can be found on codeplex: psremoteregistry.codeplex.con
Test-RegKey -ComputerName server1 -Key software\microsoft\winows -Hive LocalNachine

This site helped me. The code basically checks for one key and then checks for another one if the first one does not exist. It also verifies that the subkey exists before trying to read a value from it. If neither exist a try / catch can help deal with that.
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $computerName)
$regkey = $reg.OpenSubkey("SOFTWARE\\Symantec\\Symantec Endpoint Protection\\AV\\Storages\\Filesystem\\RealTimeScan")
if(-not $regkey) {
$regkey = $reg.OpenSubkey("SOFTWARE\\Wow6432Node\\Symantec\\Symantec Endpoint Protection\\AV\\Storages\\Filesystem\\RealTimeScan")
}
$autoProtectStatus = ""
$null = ""
if ($regkey.GetValue("OnOff", $null) -ne $null) {
$autoProtectStatus = $regkey.GetValue("OnOff")
}
if ($autoProtectStatus -eq 1) {
$autoProtectStatus = "Protection On"
} elseif ($autoProtectStatus -eq 0) {
$autoProtectStatus = "Protection Off"
} else {
$autoProtectStatus = "Unknown State"
}

So much of the Q&A is 3 years old or older. I suspect the newer versions of Powershell must have cleaned up a lot of this. Having said that, there is still not a direct command to check the registry on a remote computer (to the best of my knowledge). This works - it checks if .NET 4.6.2 is installed (is it simpler than the other answers though?)
invoke-command -computername <NetBiosName> -scriptblock {test-path -Path
"HKLM:\Software\Microsoft\.NetFramework\v4.0.30319\SKUs\.NetFramework,
Version=v4.6.2"}
You can also put the scriptblock content into a *.ps1 file (everything inside the {} and then invoke it with: Invoke-Command -ComputerName NetBiosName -FilePath "FQLP"

Related

Uninstalled application still shows in win32_product

I have an application that seems to have left a mess behind. I've uninstalled the app and it seems to have progressed normally.
No longer shows in Programs & Features. Nothing in the registry locations:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer
Nothing even when searching.
When I try to install a newer version I get an error that the previous version needs to be removed first.
I finally found something when I looked in Win32_Product using PS:
Get-WmiObject Win32_Product | Sort-Object Name | Format-Table IdentifyingNumber, Name, LocalPackage
Question is, how can I get it out of here with a script? I can't run an uninstall, get errors. I have been able to use MSI Cleanup Utility to remove but I'd like to be able to do something more automated. Estimating that there's about 200 machines in this state.
There are some very good reasons to never use Win32_Product. If you Google, there's lots of explanations, but here's one of the first hits, Please Stop Using Win32_Product To Find Installed Software. Alternatives Inside!
Of course, that's not really your question, Win32_Product was just how you located it. It's possible the program installation data is in a different location.
Try looking through:
'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
'SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\'
'Software\Classes\Local Settings\Software\Microsoft\Windows\CurrentVersion\AppModel\Repository\Packages\'
There's an awesome script on the Gallery called Get-RemoteProgram.ps1 it packages a function by the same name so dot source it into your session like:
. <Path>\Get-RemoteProgram.ps1
Once you are in you can search for the program and include the registry path in the output. There're plenty of examples in the help file but something like:
Get-RemoteProgram -ComputerName $env:computername -IncludeProgram ^Office -ProgramRegExMatch -DisplayRegPath
Once you know the location I would look for an UninstallString value. If yes, I'd then think about how to get it to run silently, which if it's an MSI package should be pretty straight forward. Once you've got it worked out simply wrap some PowerShell code around it to invoke and monitor it through to completion.
Update from Comments:
Obviously I'd have trouble figuring this out from a afar. I posted above because it would find something in the registry. Partly because you hadn't listed the Wow6432... location.
Given my earlier statements I'm not going to try testing Win32_Product on my own. However, my next step would be to figure out what Win32_Product is finding. In that case I would start with Process Monitor. It will take some work, but may illuminate what Win32_Process is finding.
The other thing I can suggest is to observe a fresh installation of the software on another system. By snapshotting the registry, and perhaps a directory listing before and after you may find additional bread crumbs.
You can also use a secondary instance of the program to harvest the uninstall string, then try running it on the concerned system to see what happens.
Piggy backing off Steven's answer, you can use something along the lines of this:
Function Uninstall-Software {
[CmdletBinding()]
[CmdletBinding()]
param(
[Parameter(Mandatory=$false,
ValueFromPipeline=$true,
ValueFromPipelineByPropertyName=$true)]
[Alias('cn','name')]
[string[]]$ComputerName,
[Parameter(Mandatory=$false)]
[switch]$Quiet
)
Begin{
$Date_Now = Get-Date
}
Process{
if($Quiet){
foreach($Computer in $ComputerName){
try{
$PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop
[array]$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"} -Session $PSSession
[array]$Software_List = $Software_List | Sort-Object -Property DisplayName -Unique
$(for($i=0; $i -lt $Software_List.Count; $i++){
[PSCustomObject]#{
'Display Name' ="${i}.) $($Software_List[$i].DisplayName)"
' Version ' = $Software_List[$i].DisplayVersion
}
} ) | Out-Host
$QS = Read-Host -Prompt "Enter Number of software to uninstall"
$QS = $QS -split ','
if([string]::IsNullOrEmpty("$QS") -eq $true){"Null string"; break}
foreach ($Q in $QS) {
if($Software_List[$Q].QuietUninstallString -eq $null){
$Quiet_Switch = '/quiet'
$Uninstall_String = $Software_List[$Q].UninstallString
Invoke-Command -ScriptBlock { & cmd /c $Using:Uninstall_String $Using:Quiet_Switch /norestart } -Session $PSSession -EnableNetworkAccess
}
else{
$Uninstall_String = $Software_List[$Q].QuietUninstallString
Invoke-Command -ScriptBlock { & cmd /c $Using:Uninstall_String /norestart } -Session $PSSession -EnableNetworkAccess
}
#& cmd /c $Uninstall_String $Quiet_Switch /norestart
}
} Catch [System.Management.Automation.RuntimeException] {
$Error[0].Message.Split('.')[1].Trim()
}
}
}
Else{
foreach($Computer in $ComputerName){
try{
$PSSession = New-PSSession -ComputerName $Computer -ErrorAction Stop
[array]$Software_List = Invoke-Command -ScriptBlock {
Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*"} -Session $PSSession
[array]$Software_List = $Software_List | Sort-Object -Property DisplayName -Unique
$(for($i=0; $i -lt $Software_List.Count; $i++){
[PSCustomObject]#{
'Display Name' ="${i}.) $($Software_List[$i].DisplayName)"
' Version ' = $Software_List[$i].DisplayVersion
}
} ) | Out-Host
$QS = Read-Host -Prompt "Enter Number of software to uninstall"
$QS = $QS -split ','
if([string]::IsNullOrEmpty("$QS") -eq $true){"Null string"; break}
foreach($Q in $QS){
$Uninstall_String = $Software_List[$Q].UninstallString
& cmd /c $Uninstall_String /norestart
}
} Catch [System.Management.Automation.RuntimeException] {
$Error[0].Message.Split('.')[1].Trim()
}
}
}
}
}
Calling the function gives you 2 options.
Interactive: Uninstall-Software
Non-Interactive: Uninstall-Software -Quiet
As you can imagine, the interactive choice doesn't work for remote computers, but using -Quiet will.
Uninstall-Software -ComputerName RemoteComputer -Quiet
This is a script I made a while ago and never finished it so it's all over the place. Works for the most part but, can use some serious work. I just dont care for it anymore.
Running the function will make a number selection out of your list of installed software pulled from the registry. So, all you have to do is select the number of software to uninstall, or select multiple.
Hopefully this gets you on the right track following Stevens comments in regards to using the uninstall string to uninstall software, instead of using CIM methods.
Please don't mark this as the answer, thought i'd share an unfinished script that works.

Install program remotely using Invoke-Command

The variable at the top of the script defines several commands/variables for New-PSDrive, as well as connection and installation.
After this, a function is created to open a text file and extract information out of it. I know this part works because I use it in 2 other scripts.
Lastly, The script executes the commands in the first variable.
The script will show as running successfully, but checking the remote computer reveals that nothing happened.
Prior to doing any of this activity, the remote computer has a script run against it that:
enables PSRemoting (setting firewall rules and starting WinRM), and
bypasses execution policies.
After those steps, the script below is run to install a piece of software.
$eAudIT2014V2Install = {
$eAudIT2014V2password = ConvertTo-SecureString "PasswordHere" -AsPlainText -Force
$eAudIT2014V2cred = New-Object System.Management.Automation.PSCredential('domain\user', $eAudIT2014V2password)
$eAudIT2014V2drive = New-PSDrive -Name eAudIT2014V2 -PSProvider FileSystem -Root "\\Server\Share" -Credential $eAudIT2014V2cred
$eAudIT2014V2job = Start-Job {"eAudIT2014V2:\Setup.cmd"}
Wait-Job $eAudIT2014V2job
Receive-Job $eAudIT2014V2job
}
Function Get-OpenFile($initialDirectory) {
[System.Reflection.Assembly]::LoadWithPartialName("System.windows.forms") |
Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.InitialDirectory = $initialDirectory
$OpenFileDialog.ShowDialog()
$OpenFileDialog.Filename
$OpenFileDialog.ShowHelp = $true
}
$InputFile = Get-OpenFile
if ($InputFile -eq "Cancel") {
Write-Host "Canceled By User"
exit
} else {
$Computers = #(Get-Content -Path $InputFile)
}
foreach ($computer in $computers) {
Write-Host "Installing eAudIT 2014V2 on Selected Computers"
Invoke-Command $eAudIT2014V2Install
}
I'm noticing that if I tell this script to run something basic like notepad.exe, a dllhost process starts on the machine, but notepad never does. What am I doing wrong?
The answer is pretty simple here. All of your script is for naught if you don't tell the Invoke-Command cmdlet what computer you want to execute the code on. As it is you are simply iterating a loop and invoking that command X number of times on the local machine. You need to change that second to the last line to specify the machine to execute the code on:
Invoke-Command $eAudIT2014V2Install -ComputerName $computer

Powershell remote returning false positive on IIS:\AppPools lookup

Whatever value entered for $appPoolName always returns true for Test-Path when sent to the remote machine, even when no such pool exists. When run locally in powershell on those machines, the proper result is returned. PSRemote is verified to be enabled on the target machines.
$appPoolName = 'Abc123'
$scriptBlock = {
Import-Module WebAdministration
if (Test-Path IIS:\AppPools\$appPoolName) {
Write-Host "Already installed."
} else {
Write-Host "Installing..."
$appPool = New-Item –Path IIS:\AppPools\$using:appPoolName
$appPool | Set-ItemProperty -Name managedRuntimeVersion -Value 'v4.0'
}
}
Invoke-Command -ComputerName LT-CODE8 -ScriptBlock $scriptBlock
Why is this reporting true, or what steps can I take to further diagnose?
I think you missed the $using: on the first call of $appPoolname:
if (Test-Path IIS:\AppPools\$using:appPoolName) {
Probably enabling cred-ssp is needed on the remote machine. Please see this thread: https://social.technet.microsoft.com/Forums/scriptcenter/en-US/3ea1ac5a-60f4-4ba1-9bdf-1119e2e5c896/testpath-fails-on-remote-computer-with-invokecommand?forum=ITCG
I had this problem when I was running Powershell in VS Code. In ISE it always worked as expected.

How to test writing to a file share path using credential?

I have an array of Credential objects and I would like to test that these credentials have permissions to write a file to a file share.
I was going to do something like
$myPath = "\\path\to\my\share\test.txt"
foreach ($cred in $credentialList)
{
"Testing" | Out-File -FilePath $myPath -Credential $cred
}
but then I discovered that Out-File doesn't take Credential as a parameter. What's the best way to solve this?
You can use New-PSDrive:
$myPath = "\\path\to\my\share"
foreach ($cred in $credentialList)
{
New-PSDrive Test -PSProvider FileSystem -Root $myPath -Credential $Cred
"Testing" | Out-File -FilePath Test:\test.txt
Remove-PSDrive Test
}
Here is asituation where an old exe (net.exe) seems to do better than powershell...
I guess you could try to map a network drive with the credential provided then test to write a file to that drive :
$cred=get-credential
$pass=$cred.GetNetworkCredential().Password
net use q: \\servername\share $pass /user:$cred.username
Use this script taken from Microsofts TechNet Script Center : http://gallery.technet.microsoft.com/scriptcenter/Lists-all-the-shared-5ebb395a
It is a lot easier to alter to fit your needs then to start completely from scratch.
Open up ListSharedFolderPermissions.ps1, and find the three $Properties vars. add a line at the top of each one so you can tell which user your looking at, so it should now look like this:
$Properties = #{'Username' = $Credential.UserName
'ComputerName' = $ComputerName
. . . . . }
Next, add your new Username property to the select-object line (3 times) :
$Objs|Select-Object Username,ComputerName,ConnectionStatus,SharedFolderName,SecurityPrincipal, `
FileSystemRights,AccessControlType
Once youve added those small pieces in the six appropriate places your script is ready to use:
cd c:\Path\where\you\put\ps1\file
$permissions = #()
$myPath = "computername"
foreach ($cred in $credentialList)
{
$permissions += .\ListAllSharedFolderPermission.ps1 -ComputerName $myPath -Credential $cred
$permissions += " "
}
$permissions | Export-Csv -Path "C:\Permission.csv" -NoTypeInformation
Try using the Invoke-Command function. It will take a credential object and allow you to run an arbitrary script block under that command. You can use that to test out writing the file
Invoke-Command -ScriptBlock { "Testing" | Out-File $myPath } -Credential $cred
I think the Invoke-command approach should work. But if nothing works you can try the powershell impersonation module. It successfully impersonates a user for most Powershell commands without the -Credential switch.
A few ideas:
Create your own PowerShell Provider
Impersonate a user and then write to the share (not sure if possible in powershell)
Use net use d:... as #Kayasax has suggested
Use WScript.Network
I'm very interested in the PowerShell provider myself, but I decided to make something real quick so I went with using the WScript.Network library. I used a hash table to track whether a user would be "authenticated" or not.
$credentials = #() # List of System.Net.NetworkCredential objects
$authLog = #{}
$mappedDrive = 'z:'
$tmpFile = $mappedDrive, '\', [guid]::NewGuid(), '.tmp' -join ''
$path = [io.path]::GetPathRoot('\\server\share\path')
$net = new-object -comObject WScript.Network
foreach ($c in $credentials) {
if ($authLog.ContainsKey($c.UserName)) {
# Skipping because we've already tested this user.
continue
}
try {
if (Test-Path $mappedDrive) {
$net.RemoveNetworkDrive($mappedDrive, 1) # 1 to force
}
# Attempt to map drive and write to it
$net.MapNetworkDrive($mappedDrive, $path, $false, $c.UserName, $c.Password)
out-file $tmpFile -inputObject 'test' -force
# Cleanup
Remove-Item $tmpFile -force
$net.RemoveNetworkDrive($mappedDrive, 1)
# Authenticated.
# We shouldn't have reached this if we failed to mount or write
$authLog.Add($c.UserName, 'Authorized')
}
catch [Exception] {
# Unathenticated
$authLog.Add($c.UserName, 'Unauthorized')
}
}
$authLog
# Output
Name Value
---- -----
desktop01\user01 Authorized
desktop01\user02 Unauthorized

Yesterday afternoon I was able to access WMI, this morning I cannot access WMI, and vice versa

My powershell script determines the current user of a remote Windows 7 computer and will output
userId=DOMAIN\username
If there is no user currently logged on, the script will output
userId=No One Currently Logged In
And if the script cannot access the WMI of the remote computer, the script will output
userId=CannotConnectToWMI
I ran the script along with running WBEMTEST to confirm whether or not WMI can be accessed on the remote machine.
I am really puzzled because yesterday afternoon, I was able to access WMI on several remote machines, and this morning, I cannot. Below is a chart:
Why is this happening?
How to make sure that WMI is always accessible? I posted another question yesterday about WMI, https://stackoverflow.com/questions/19409747/wbemtest-to-windows-7-says-the-rpc-server-is-unavailable
Please help
#vonPryz
The script has Test-Connection. Below is the entire script
$line_array = #()
$multi_array = #()
[hashtable]$my_hash = #{}
$Sender_IP = $NULL
$bios = $NULL
$wmi = $NULL
foreach ($i in $args){
$line_array+= $i.split(" ")
}
foreach ($j in $line_array){
$multi_array += ,#($j.split("="))
}
foreach ($k in $multi_array){
$my_hash.add($k[0],$k[1])
}
$Sender_IP = $my_hash.Get_Item("sender-ip")
try{
Test-Connection $Sender_IP -count 1 -ErrorAction Stop | out-null
}
catch [Exception]
{
$userId = "userId=CannotPing"
return $userId
}
try{
$wmi = gwmi -class win32_computerSystem -computer $Sender_IP -ErrorAction Stop
}
catch [Exception]{
$userId = "userId=CannotConnectToWMI"
return $userId
}
try{
$userId = ($wmi).username
}
catch [Exception]{
$userId = "userId=CannotFindLastUserLoggedOn"
return $userId
}
if ($userId -ne $NULL){
$userID = "userId="+$userId
return $userId
}
elseif ($userID -eq $NULL)
{
$userId = "userId=No One Currently Logged In"
return $userId
}
EDIT
I was remoting into these computers to check DCOM permissions, and then I realized that one of them turned into Windows XP. It seems that the IP addresses are getting switched to different computers. I will compare according to Fully Qualified Domain Name.
Add Test-Connection to your script and try WMI only if pinging the host is succesfull.
I am currently testing several IP addresses that are Windows 7. When I remoted into one of the troublesome IP addresses, I noticed it became Windows XP. Then I realized that IP addresses of computer get changed every few days, so 10.10.10.10 may belong to ComputerA.contoso.com one day, and few days later, may belong to ComputerB.contoso.com.
Now before I do any testing on a bunch of computers, I go according to their Fully Qualified Domain Name, and then find the corresponding IP address before performing any testing.