Querying via powershell both 32bit and 64bit registry - windows

I am running a script that queries windows and its registry.
I'm trying to add a code where it can query both 64bit and 32bit versions of the OS.
So if it's a 32bit then it should look at HKLM_SOFTWARE_TEAMVIEWER
and if it's 64bit it should query at HKLM_SOFTWARE_WOW6432Node_Teamviewer
So, how should this part look to query both locations depending on OS type?
$TVID = (Get-ItemProperty "HKLM:\SOFTWARE\TeamViewer").ClientID
This is the script:
Param(
[string]$ServerShare
)
$dom = $env:userdomain
$usr = $env:username
$Fullname = ([adsi]"WinNT://$dom/$usr,user").fullname
$TVID = (Get-ItemProperty "HKLM:\SOFTWARE\TeamViewer").ClientID
if (!$TVID) { $TVID = (Get-ItemProperty "HKLM:\SOFTWARE\TeamViewer\Version9").ClientID }

Apart from first detecting what bitness the computer uses, there is a simpler way I think by testing any of the two possible registry paths like:
# get the existing registry path (if any)
$regPath = 'HKLM:\SOFTWARE\TeamViewer', 'HKLM:\SOFTWARE\WOW6432Node\TeamViewer' | Where-Object { Test-Path -Path $_ }
if ($regPath) {
# we found the path, get the ClientID value
$TVID = (Get-ItemProperty -Path $regPath).ClientID
}
else {
Write-Warning "TeamViewer registry path not found"
}

You can check WMI under Win32_Processor and look at the process AddressWidth property to check your OS CPU AddressWidth.
#determine process version
[boolean]$is64bit = [boolean]((Get-WmiObject -Class "Win32_Processor" |
Where-Object {$_.DeviceID -eq 'CPU0'} | Select -ExpandProperty AddressWidth) -eq 64)
if ($is64bit){
#look here for 64 bit reg keys
Write-Output "x64 bit os detected"
}
else{
#look here for 32 bit reg keys
Write-Output " 32 bit os detected"
}
And run on my system
x64 bit OS Detected
Now all you need to do is merge your registry fetch code into the proper spots and you're on your way...

The easiest way to check OS bittness is to use .net.
[Environment]::Is64BitOperatingSystem

Related

Is there a equivalent pwdx in windows? Or some workaround?

I just want to determine the working directory of a running process. In Linux you can use pwdx, but i cant find a similar tool in windows. It would be perfect to get a commandline solution.
I tried nearly every possible command for windows. wmic gwmic taskkill.. none of them is a solution for my problem.
The Get-Process command in PowerShell gives you the equivalent information (at least the path of the .exe)
>Get-Process ssms | Select -Expand Path | Split-path
C:\Program Files (x86)\Microsoft SQL Server\130\Tools\Binn\ManagementStudio
You could make this your own function, if you wanted:
Function pwdx{
param($Process)
Get-Process $Process | Select -Expand Path |Split-Path
}
C:\Users\FoxDeploy> pwdx notepad
C:\WINDOWS\system32
If you need to do this in the command line in Windows, you can find the process this way.
wmic process where "name like '%notepad%'" get ExecutablePath
ExecutablePath
C:\WINDOWS\system32\notepad.exe
If you want to receive the same information as the Process Explorer you can use a Windows Management Instrumentation query on the process.
Function Query-Process-WMI
{
param
(
[Parameter( Mandatory = $true )]
[String]
$processName
)
$process = Get-Process $processName
$processInfo = $process | ForEach-Object { Get-WmiObject Win32_Process -Filter "name = '$($_.MainModule.ModuleName)'" }
$processInfoCommandLine = $processInfo | ForEach-Object { $_.CommandLine }
return $processInfoCommandLine
}

Get list of installed software of remote computer

Is it possible to get a list of installed software of a remote computer ?
I know to do this for a local computer with use of Powershell. Is it possible with Powershell to get installed software of a remote computer and save this list on the remote computer ?
This I use for local computers:
Get-WmiObject -Class Win32_Product | Select-Object -Property Name
Thanks in advance,
Best regards,
This uses Microsoft.Win32.RegistryKey to check the SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall registry key on remote computers.
https://github.com/gangstanthony/PowerShell/blob/master/Get-InstalledApps.ps1
*edit: pasting code for reference
function Get-InstalledApps {
param (
[Parameter(ValueFromPipeline=$true)]
[string[]]$ComputerName = $env:COMPUTERNAME,
[string]$NameRegex = ''
)
foreach ($comp in $ComputerName) {
$keys = '','\Wow6432Node'
foreach ($key in $keys) {
try {
$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey('LocalMachine', $comp)
$apps = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall").GetSubKeyNames()
} catch {
continue
}
foreach ($app in $apps) {
$program = $reg.OpenSubKey("SOFTWARE$key\Microsoft\Windows\CurrentVersion\Uninstall\$app")
$name = $program.GetValue('DisplayName')
if ($name -and $name -match $NameRegex) {
[pscustomobject]#{
ComputerName = $comp
DisplayName = $name
DisplayVersion = $program.GetValue('DisplayVersion')
Publisher = $program.GetValue('Publisher')
InstallDate = $program.GetValue('InstallDate')
UninstallString = $program.GetValue('UninstallString')
Bits = $(if ($key -eq '\Wow6432Node') {'64'} else {'32'})
Path = $program.name
}
}
}
}
}
}
There are multiple ways how to get the list of installed software on a remote computer:
Running WMI query on ROOT\CIMV2 namespace:
Start WMI Explorer or any other tool which can run WMI queries.
Run WMI query "SELECT * FROM Win32_Product"
Using wmic command-line interface:
Press WIN+R
Type "wmic", press Enter
In wmic command prompt type "/node:RemoteComputerName product"
Using Powershell script:
Thru WMI object: Get-WmiObject -Class Win32_Product -Computer RemoteComputerName
thru Registry: Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall* | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Format-Table –AutoSize
thru Get-RemoteProgram cmdlet: Get-RemoteProgram -ComputerName RemoteComputerName
Source: https://www.action1.com/kb/list_of_installed_software_on_remote_computer.html
Here is an article detailing how to query the list of installed programs locally and remotely.
Also, it will interest you to know the reason why the Get-WmiObject cmdlet is working anymore. It has been superseded with the CimInstance cmdlet.
https://techdirectarchive.com/2020/12/22/how-to-get-a-list-of-the-installed-program-using-powershell-in-windows-10/
When working with the CimInstance cmdlet and you encounter this error "WinRM cannot complete the operation, verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled", I have described the steps to have it resolved in this link: WinRM cannot complete the operation, verify that the specified computer name is valid, that the computer is accessible over the network, and that a firewall exception for the WinRM service is enabled.
https://techdirectarchive.com/2023/02/03/winrm-cannot-complete-the-operation-verify-that-the-specified-computer-name-is-valid-that-the-computer-is-accessible-over-the-network/
No one seems to know about get-package in powershell 5.1. You'll have to use invoke-command to run it on a remote computer.
get-package | more
Name Version Source ProviderName
---- ------- ------ ------------
7-Zip 21.07 (x64) 21.07 Programs
Wolfram Extras 11.0 (5570611) 11.0.0 Programs
ArcGIS Desktop Background G... 10.8.12790 Programs
# and so on...

Powershell, How to get date of last Windows update install or at least checked for an update?

I am trying to find a way of retrieving the date/time of which the last windows update was either installed, or checked for.
So far I have found a function that allows to list recent Windows Updates, but it is far too much data and too bloated for such a simple function. Secondly I have tried to access the registry although I am having no luck in retriving the value I am after.
I am testing this on a Windows 10 Machine although the software will probably reside on Windows Server 2012 R2.
Here is an example of some of the code I have tried:
$key = “SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install”
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$RemoteBase = [Microsoft.Win32.RegistryKey]::OpenBaseKey($keytype,"My Machine")
$regKey = $RemoteBase.OpenSubKey($key)
$KeyValue = $regkey.GetValue(”LastSuccessTime”)
$System = (Get-Date -Format "yyyy-MM-dd hh:mm:ss")
Also, just trying the Get-ChildItem
$hello = Get-ChildItem -Path “hkcu:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\”
foreach ($a in $hello) {
$a
}
I've checked in regedit and this key does not exist. Going to the "Windows Update" path shows only App Updates and not Windows updates.
EDIT
I seem to be closer to my goal with this line:
Get-HotFix | Where {$_.InstallDate -gt 30}
However how to I only retrive those of which have been installed in the last 30 days? And this doesnt show many results, even using Select $_.InstallDate
an option :
gwmi win32_quickfixengineering |sort installedon -desc
Another alternative, using the com object Microsoft.Update.Session can be find here : https://p0w3rsh3ll.wordpress.com/2012/10/25/getting-windows-updates-installation-history/
in short :
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$HistoryCount = $Searcher.GetTotalHistoryCount()
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa386532%28v=vs.85%29.aspx
$Searcher.QueryHistory(0,$HistoryCount) | ForEach-Object {$_}
Here you have how to know the date and time of the last Windows update in a single line of Powershell:
(New-Object -com "Microsoft.Update.AutoUpdate"). Results | fl
You also have the following script to check it massively in Windows Server:
$ servers = Get-ADComputer -Filter {(OperatingSystem-like "* windows * server *") -and (Enabled -eq "True")} -Properties OperatingSystem | Sort Name | select -Unique Name
foreach ($ server in $ servers) {
write-host $ server.Name
Invoke-Command -ComputerName $ server.Name -ScriptBlock {
(New-Object -com "Microsoft.Update.AutoUpdate"). Results}
}
Extracted from: https://www.sysadmit.com/2019/03/windows-update-ver-fecha-powershell.html
Get-HotFix |?{$_.InstalledOn -gt ((Get-Date).AddDays(-30))}
Using PowerShell, you can get the date of the las Windows update like this:
$lastWindowsUpdate = (Get-Hotfix | Sort-Object -Property InstalledOn -Descending | Select-Object -First 1).InstalledOn

Network drives mapping from XP to 7

I am migrating multiple users from XP to 7 and they all have different mapped drives/locations on their current PC. After copying their all data from old PC to new PC, I am currently manually mapping their drives which consumes lot of time. Is there anyway of automating this process?
Is there any way of running a script on existing XP machine and running the same script on new Win 7 machine to map all the drives?
I am looking for a script or any other way of automating this process.
Thanks.
You could do this for all your users, it would at least tell you what they had.
you'd probably want one central folder, lets say Mappings, so try
net use > \servername\Mappings\%username%_map.txt
Or try something like this
http://www.visualbasicscript.com/List-mapped-drives-on-remote-machine-m28529.aspx
out of boredom i quickly wrote a powershell script to help you out.
Run this on your workstation:
(newpcs and oldpcs must be in correct order so oldpc1 is the old pc of the user of newpc1)
$oldpcs=#("oldpc1", "oldpc2", "oldpc3")
$newpcs = #("newpc1", "newpc2", "newpc3")
$mapping = #{}
for($i=0;$i -lt $oldpcs.Count; $i++){
$mapping.add($oldpcs[$i], $newpcs[$i])
}
foreach ($comp in $oldpcs){
$m = Get-WmiObject win32_systemnetworkconnections -ComputerName $comp
$m | %{
#i know this is not very elegant but whatever
$temp = $_.partcomponent -split "="
$temp = $temp -replace "`"", ""
$temp2= $temp[1] -split " "
$driveletter = $temp2[1] -replace "\(", ""
$driveletter = $driveletter -replace "\)", ""
$path = $temp2[0] -replace "\\\\", "\"
$f = "C:\path\to\folder\" + $mapping.$comp + ".txt"
Add-Content $f "$driveletter;$path"
}
}
Then get the file with corresponding computername to the new computer and run the following:
$txt = Get-Content "C:\path\to\file\$env:computername.txt"
$txt | % {
$temp = $_ -split ";"
net use $temp[0] $temp[1]
}
Remember that you have to run the mapping-script in the context of the user you want to map the drives for
Regards
P.S. Remotely mapping network drives is not possible afaik (i would love to be proven wrong)
You could create a logon script and map it to a user though

How can I uninstall an application using PowerShell?

Is there a simple way to hook into the standard 'Add or Remove Programs' functionality using PowerShell to uninstall an existing application? Or to check if the application is installed?
$app = Get-WmiObject -Class Win32_Product | Where-Object {
$_.Name -match "Software Name"
}
$app.Uninstall()
Edit: Rob found another way to do it with the Filter parameter:
$app = Get-WmiObject -Class Win32_Product `
-Filter "Name = 'Software Name'"
EDIT: Over the years this answer has gotten quite a few upvotes. I would like to add some comments. I have not used PowerShell since, but I remember observing some issues:
If there are more matches than 1 for the below script, it does not work and you must append the PowerShell filter that limits results to 1. I believe it's -First 1 but I'm not sure. Feel free to edit.
If the application is not installed by MSI it does not work. The reason it was written as below is because it modifies the MSI to uninstall without intervention, which is not always the default case when using the native uninstall string.
Using the WMI object takes forever. This is very fast if you just know the name of the program you want to uninstall.
$uninstall32 = gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString
$uninstall64 = gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match "SOFTWARE NAME" } | select UninstallString
if ($uninstall64) {
$uninstall64 = $uninstall64.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall64 = $uninstall64.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall64 /qb" -Wait}
if ($uninstall32) {
$uninstall32 = $uninstall32.UninstallString -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstall32 = $uninstall32.Trim()
Write "Uninstalling..."
start-process "msiexec.exe" -arg "/X $uninstall32 /qb" -Wait}
To fix up the second method in Jeff Hillman's post, you could either do a:
$app = Get-WmiObject
-Query "SELECT * FROM Win32_Product WHERE Name = 'Software Name'"
Or
$app = Get-WmiObject -Class Win32_Product `
-Filter "Name = 'Software Name'"
One line of code:
get-package *notepad* |% { & $_.Meta.Attributes["UninstallString"]}
function Uninstall-App {
Write-Output "Uninstalling $($args[0])"
foreach($obj in Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall") {
$dname = $obj.GetValue("DisplayName")
if ($dname -contains $args[0]) {
$uninstString = $obj.GetValue("UninstallString")
foreach ($line in $uninstString) {
$found = $line -match '(\{.+\}).*'
If ($found) {
$appid = $matches[1]
Write-Output $appid
start-process "msiexec.exe" -arg "/X $appid /qb" -Wait
}
}
}
}
}
Call it this way:
Uninstall-App "Autodesk Revit DB Link 2019"
I found out that Win32_Product class is not recommended because it triggers repairs and is not query optimized. Source
I found this post from Sitaram Pamarthi with a script to uninstall if you know the app guid. He also supplies another script to search for apps really fast here.
Use like this: .\uninstall.ps1 -GUID
{C9E7751E-88ED-36CF-B610-71A1D262E906}
[cmdletbinding()]
param (
[parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
[string]$ComputerName = $env:computername,
[parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true,Mandatory=$true)]
[string]$AppGUID
)
try {
$returnval = ([WMICLASS]"\\$computerName\ROOT\CIMV2:win32_process").Create("msiexec `/x$AppGUID `/norestart `/qn")
} catch {
write-error "Failed to trigger the uninstallation. Review the error message"
$_
exit
}
switch ($($returnval.returnvalue)){
0 { "Uninstallation command triggered successfully" }
2 { "You don't have sufficient permissions to trigger the command on $Computer" }
3 { "You don't have sufficient permissions to trigger the command on $Computer" }
8 { "An unknown error has occurred" }
9 { "Path Not Found" }
9 { "Invalid Parameter"}
}
To add a little to this post, I needed to be able to remove software from multiple Servers. I used Jeff's answer to lead me to this:
First I got a list of servers, I used an AD query, but you can provide the array of computer names however you want:
$computers = #("computer1", "computer2", "computer3")
Then I looped through them, adding the -computer parameter to the gwmi query:
foreach($server in $computers){
$app = Get-WmiObject -Class Win32_Product -computer $server | Where-Object {
$_.IdentifyingNumber -match "5A5F312145AE-0252130-432C34-9D89-1"
}
$app.Uninstall()
}
I used the IdentifyingNumber property to match against instead of name, just to be sure I was uninstalling the correct application.
Here is the PowerShell script using msiexec:
echo "Getting product code"
$ProductCode = Get-WmiObject win32_product -Filter "Name='Name of my Software in Add Remove Program Window'" | Select-Object -Expand IdentifyingNumber
echo "removing Product"
# Out-Null argument is just for keeping the power shell command window waiting for msiexec command to finish else it moves to execute the next echo command
& msiexec /x $ProductCode | Out-Null
echo "uninstallation finished"
I will make my own little contribution. I needed to remove a list of packages from the same computer. This is the script I came up with.
$packages = #("package1", "package2", "package3")
foreach($package in $packages){
$app = Get-WmiObject -Class Win32_Product | Where-Object {
$_.Name -match "$package"
}
$app.Uninstall()
}
I hope this proves to be useful.
Note that I owe David Stetler the credit for this script since it is based on his.
Based on Jeff Hillman's answer:
Here's a function you can just add to your profile.ps1 or define in current PowerShell session:
# Uninstall a Windows program
function uninstall($programName)
{
$app = Get-WmiObject -Class Win32_Product -Filter ("Name = '" + $programName + "'")
if($app -ne $null)
{
$app.Uninstall()
}
else {
echo ("Could not find program '" + $programName + "'")
}
}
Let's say you wanted to uninstall Notepad++. Just type this into PowerShell:
> uninstall("notepad++")
Just be aware that Get-WmiObject can take some time, so be patient!
Use:
function remove-HSsoftware{
[cmdletbinding()]
param(
[parameter(Mandatory=$true,
ValuefromPipeline = $true,
HelpMessage="IdentifyingNumber can be retrieved with `"get-wmiobject -class win32_product`"")]
[ValidatePattern('{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}}')]
[string[]]$ids,
[parameter(Mandatory=$false,
ValuefromPipeline=$true,
ValueFromPipelineByPropertyName=$true,
HelpMessage="Computer name or IP adress to query via WMI")]
[Alias('hostname,CN,computername')]
[string[]]$computers
)
begin {}
process{
if($computers -eq $null){
$computers = Get-ADComputer -Filter * | Select dnshostname |%{$_.dnshostname}
}
foreach($computer in $computers){
foreach($id in $ids){
write-host "Trying to uninstall sofware with ID ", "$id", "from computer ", "$computer"
$app = Get-WmiObject -class Win32_Product -Computername "$computer" -Filter "IdentifyingNumber = '$id'"
$app | Remove-WmiObject
}
}
}
end{}}
remove-hssoftware -ids "{8C299CF3-E529-414E-AKD8-68C23BA4CBE8}","{5A9C53A5-FF48-497D-AB86-1F6418B569B9}","{62092246-CFA2-4452-BEDB-62AC4BCE6C26}"
It's not fully tested, but it ran under PowerShell 4.
I've run the PS1 file as it is seen here. Letting it retrieve all the Systems from the AD and trying to uninstall multiple applications on all systems.
I've used the IdentifyingNumber to search for the Software cause of David Stetlers input.
Not tested:
Not adding ids to the call of the function in the script, instead starting the script with parameter IDs
Calling the script with more then 1 computer name not automatically retrieved from the function
Retrieving data from the pipe
Using IP addresses to connect to the system
What it does not:
It doesn't give any information if the software actually was found on any given system.
It does not give any information about failure or success of the deinstallation.
I wasn't able to use uninstall(). Trying that I got an error telling me that calling a method for an expression that has a value of NULL is not possible. Instead I used Remove-WmiObject, which seems to accomplish the same.
CAUTION: Without a computer name given it removes the software from ALL systems in the Active Directory.
For Most of my programs the scripts in this Post did the job.
But I had to face a legacy program that I couldn't remove using msiexec.exe or Win32_Product class. (from some reason I got exit 0 but the program was still there)
My solution was to use Win32_Process class:
with the help from nickdnk this command is to get the uninstall exe file path:
64bit:
[array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString
32bit:
[array]$unInstallPathReg= gci "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" | foreach { gp $_.PSPath } | ? { $_ -match $programName } | select UninstallString
you will have to clean the the result string:
$uninstallPath = $unInstallPathReg[0].UninstallString
$uninstallPath = $uninstallPath -Replace "msiexec.exe","" -Replace "/I","" -Replace "/X",""
$uninstallPath = $uninstallPath .Trim()
now when you have the relevant program uninstall exe file path you can use this command:
$uninstallResult = (Get-WMIObject -List -Verbose | Where-Object {$_.Name -eq "Win32_Process"}).InvokeMethod("Create","$unInstallPath")
$uninstallResult - will have the exit code. 0 is success
the above commands can also run remotely - I did it using invoke command but I believe that adding the argument -computername can work
For msi installs, "uninstall-package whatever" works fine. For non-msi installs (Programs provider), it takes more string parsing. This should also take into account if the uninstall exe is in a path with spaces and is double quoted. Install-package works with msi's as well.
$uninstall = get-package whatever | % { $_.metadata['uninstallstring'] }
# split quoted and unquoted things on whitespace
$prog, $myargs = $uninstall | select-string '("[^"]*"|\S)+' -AllMatches |
% matches | % value
$prog = $prog -replace '"',$null # call & operator doesn't like quotes
$silentoption = '/S'
$myargs += $silentoption # add whatever silent uninstall option
& $prog $myargs # run uninstaller silently
Start-process doesn't mind the double quotes, if you need to wait anyway:
# "C:\Program Files (x86)\myapp\unins000.exe"
get-package myapp | foreach { start -wait $_.metadata['uninstallstring'] /SILENT }
On more recent windows systems, you can use the following to uninstall msi installed software. You can also check $pkg.ProviderName -EQ "msi" if you like.
$pkg = get-package *name*
$prodCode = "{" + $pkg.TagId + "}"
msiexec.exe /X $prodCode /passive

Resources