UPDATE(15/7/2015): This script is updated recently to query 32-bit as well as 64-bit applications installed on remote computers. It also provides an extra column in the output which indicates the architecture(x86 or x64) of the software.
Recently I came across a forum question where I have seen people using Win32_Product WMI class to get the installed installed applications list from remote computers. Historically, Win32_Product proved to be a buggy one due to various factors (which I will talk in later posts). So, I didn’t recommend them to use Win32_Product WMI class. If not WMI, what are the various options available to get the installed problems from remote computers. There are two methods (1) Using Win32Reg_AddRemovePrograms (2) Using Registry(uninstallkey).
The Win32Reg_AddRemovePrograms is not a common WMI class that you will find in any windows computer. It comes along with SMS or SCCM agent installation. So if you have one of these agents then probably you can explore this method. Otherwise, we need to rely on registry to get this information. Is the information queried from registry is accurate? My answer is YES and NO. It gives all the applications installed by both MSI installer and executables. But some of the registry keys will have less information about the software — not sure why it is that way.
Keeping the downsides aside, it is definitely the best approach to get installed softwares from remote computer. I am also excluding the applications from display if their display name black(which makes sense). This script will return the uninstall string as well which is essential for uninstalling the software. I will use this in my upcoming posts to uninstall a software remotely.
[cmdletbinding()]param([parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string[]]$ComputerName=$env:computername)begin{$UninstallRegKeys=@("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall","SOFTWARE\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall")}process{foreach($Computerin$ComputerName){Write-Verbose"Working on $Computer"if(Test-Connection-ComputerName$Computer-Count1-ea0){foreach($UninstallRegKeyin$UninstallRegKeys){try{$HKLM=[microsoft.win32.registrykey]::OpenRemoteBaseKey('LocalMachine',$computer)$UninstallRef=$HKLM.OpenSubKey($UninstallRegKey)$Applications=$UninstallRef.GetSubKeyNames()}catch{Write-Verbose"Failed to read $UninstallRegKey"Continue}foreach($Appin$Applications){$AppRegistryKey=$UninstallRegKey+"\\"+$App$AppDetails=$HKLM.OpenSubKey($AppRegistryKey)$AppGUID=$App$AppDisplayName=$($AppDetails.GetValue("DisplayName"))$AppVersion=$($AppDetails.GetValue("DisplayVersion"))$AppPublisher=$($AppDetails.GetValue("Publisher"))$AppInstalledDate=$($AppDetails.GetValue("InstallDate"))$AppUninstall=$($AppDetails.GetValue("UninstallString"))if($UninstallRegKey-match"Wow6432Node"){$Softwarearchitecture="x86"}else{$Softwarearchitecture="x64"}if(!$AppDisplayName){continue}$OutputObj=New-Object-TypeNamePSobject$OutputObj|Add-Member-MemberTypeNoteProperty-NameComputerName-Value$Computer.ToUpper()$OutputObj|Add-Member-MemberTypeNoteProperty-NameAppName-Value$AppDisplayName$OutputObj|Add-Member-MemberTypeNoteProperty-NameAppVersion-Value$AppVersion$OutputObj|Add-Member-MemberTypeNoteProperty-NameAppVendor-Value$AppPublisher$OutputObj|Add-Member-MemberTypeNoteProperty-NameInstalledDate-Value$AppInstalledDate$OutputObj|Add-Member-MemberTypeNoteProperty-NameUninstallKey-Value$AppUninstall$OutputObj|Add-Member-MemberTypeNoteProperty-NameAppGUID-Value$AppGUID$OutputObj|Add-Member-MemberTypeNoteProperty-NameSoftwareArchitecture-Value$Softwarearchitecture$OutputObj}}}}}end{}
Copy this code to a text file and save it as Get-InstalledSoftware.ps1. Look at the below image for usage.
Whenever a computer is added to a windows domain, by default account will get created under Computers container. It is located right below the domain name in dsa.msc. The pull of it is, <domainName>\Computers.
Today one of my friend has asked to know if there any quick script using which he can move all computers from default computers container to OU of his choice in same domain. Since I don’t have a script already authored for this purpose, I quickly made the below.
This is a very basic version of computer accounts movement script. There might be some conditions processing like if name contains XYZ move to one OU or if name contains ABC move to different OU. You can accommodate such conditions in this script if you have little powershell knowledge(or let me know I can help you given some time).
Here is the code.
[cmdletbinding()]param([parameter(mandatory=$true)]$TargetOU)Import-ModuleActiveDirectory$Domain=[ADSI]""$DN=$domain.distinguishedName$SourcePath="CN=Computers,"+$DN$Computers=Get-ADComputer-Filter*-SearchBase$SourcePathif(!$Computers){write-host"No Computers are found in default container"return}foreach($Computerin$Computers){if(!(Move-ADObject$Computer-TargetPath$TargetOU)){$Status="SUCCESS"}else{$Status="FAILED"}$OutputObj=New-Object-TypeNamePSobject$OutputObj|Add-Member-MemberTypeNoteProperty-NameComputerName-Value$Computer.Name.tostring()$OutputObj|Add-Member-MemberTypeNoteProperty-NameSourcePath-Value$SourcePath$OutputObj|Add-Member-MemberTypeNoteProperty-NameDestinationPath-Value$TargetOU$OutputObj|Add-Member-MemberTypeNoteProperty-NameStatus-Value$Status$OutputObj}
When I executed this script in my test domain for testing purpose it went fine and generated below output. This script is not depending on any external modules/cmdlets like quest tools. I uses ActiveDirectory module which comes with RSAT(or windows 2008 domain controllers). Needless to say that you need ADWS(active directory web services) installed if all your domain controllers are Windows 2003. This is not required if atleast one DC is having windows 2008 R2 OS where ADWS is default.
I am a very bad user of my PC. I open many IE, notepad and other application windows sand leave them like that. I only care to close them only when my computer starts behaving crazy :-/
Today I did the same exercise of closing old/unused application windows to give some breathing room for my computer. While doing it I was curios to know what is the count of each process running on my computer so that I can see the top processes and close the ones which are high in number. And immediately, I authored this script.
All I am doing is reading the process names into a array called $myprocesses and applying the one of my previous powershell inventions which can detect the duplicates in an array and give the count. This one proved to be handy for me many times.
So, after executing the script, I can see that too many notepad.exe processes are running….. Thank you PS. I will close them first. 🙂
Thank you for reading. Let me know if you come across this small tip handy anywhere.
As I mentioned in one of the recent posts, MS has released a PST capture tool that helps you migrate your users PST to Exchange 2010 archive mailboxes or Exchange online storage. Though I read some documentation of this tool to understand what it is and how it works, I could able to get clear idea only after reading below articles.
They covers the good amount of details in installing PST central service and deploying the agents for scanning the PST files on computers.
What are the IP address details of remote computer? When even I came across this question while troubleshooting some problem, I do nothing but logging on to the servers to see the details. Isn’t this a time consuming process? what if I want to get these details using some automation. Unfortunately, there is no windows built-in command like ipconfig /system:mypc1 to get the IP details. So, I decided to make a powershell script which addresses this purpose.
This script is pretty much self explanatory. It uses Win32_NetworkAdapterConfiguration WMI class to get the network configuration details. This script also helps you to get DNS servers, MAC address details, subnetmask and default gateway details. Using this script you can also know the list of network adapters that has DHCP enabled/disabled(means static IPs).
You can save this script to a PS1 fil(say Get-IPDetails.PS1) and run it against list of computers you need. Below is one example.
Want to know how to shutdown/restart a remote computer using Powershell? Well, this post is for same purpose. Powershell has built-in cmdlets to shutdown/restart remote computers –i.e shutdown-computer, restart-computer. They servers most purpose, but the problem is that these cmdlets doesn’t provide a way to give shutdown or restart comments and the timeout.
Having the ability to provide comments (or reason for shutdown/restart) is important and useful since we can track how has initiated the reboot.
Now let us jump on to the coding part.
[cmdletbinding()]param([parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string[]]$ComputerName=$env:computername,$Timeout=30,[switch]$Reboot)begin{$Username=$env:usernameif($reboot){$flag=2}else{$flag=1}if($reboot){$comment="Reboot initiated by $Username using $($MyInvocation.InvocationName). Timeout is $Timeout"}else{$comment="Reboot initiated by $Username using $($MyInvocation.InvocationName). Timeout is $Timeout"}}process{foreach($Computerin$ComputerName){Write-Verbose"Working on $Computer"if(Test-Connection-ComputerName$Computer-Count1-ea0){$OS=Get-WMIObject-ClassWin32_OperatingSystem-ComputerName$Computerif(-not$OS.Win32ShutdownTracker($timeout,$comment,0,$flag)){$Status="FAILED"}else{$Status="SUCCESS"}$OutputObj=New-Object-TypeNamePSobject$OutputObj|Add-Member-MemberTypeNoteProperty-NameComputerName-Value$Computer$OutputObj|Add-Member-MemberTypeNoteProperty-NameStatus-Value$status$OutputObj|Add-Member-MemberTypeNoteProperty-NameTimeout-Value$timeout$OutputObj}}}end{}
This script uses WMI class Win32_OperatingSystem to shutdown the computer. This class has a method Win32ShutdownTracker which is available in Vista, Windows 7, Windows 2008(and R2) operating systems that allows us to provide a comment and timeout for the shutdown and restart. This script provides a default timeout of 30s for shutdown/restart operations but you can reduce or increase it by using -Timeout parameter. Setting this parameter to 0(zero) will trigger the graceful shutdown immediately.
You might get alerts your DFS-R management pack in SCOM about the DFS-R service inability to register itself with WMI and it can impact the replication. Exact error message is given below.
DFS Replication failed to register itself with WMI. Replication is disabled until WMI registration succeeds.
To resolve this issue, you may need to re-register DFS-R related DLL and other files that belongs to WMI. Before going there, just restart your DFS-R service once to confirm that your problem is not transient and happening all time. You will notice a error message with 6104 event ID in DFS Replication event log after you restart the service. If this event is present, then problem is live and you should fix it.
The following solution worked for me.
CD %windir%\system32\wbem
mofcomp dfsrprov.mof
mofcomp dfsrprov.mfl
wmiprvse /regserver
net stop dfsr
net start dfsr
All it does is re-register the WMI related files of DFS-R.
If you still notice issues, you may want to re-register everything under WBEM folder. This was suggested in Technet Forum.
CD %windir%\system32\wbem For /f %s in (‘dir /b /s *.dll’) do regsvr32 /s %s for /f %s in (‘dir /b *.mof *.mfl’) do mofcomp %s wmiprvse /regserver net stop dfsr net start dfsr
if the problem is still not resolved, try rebooting the DFS-R server.
Sometimes, the compilation of MOF files might fail with below errors while performing above steps. In such cases, http://support.microsoft.com/kb/841619 might help you.
C:\WINNT\system32\wbem>mofcomp dfsrprov.mof Microsoft (R) 32-bit MOF Compiler Version 5.2.3790.3959 Copyright (c) Microsoft Corp. 1997-2001. All rights reserved. Parsing MOF file: dfsrprov.mof MOF file has been successfully parsed Storing data in the repository… An error occurred while opening the namespace for object 1 defined on lines 4 – 4: Error Number: 0x8007000e, Facility: Win32 Description: Not enough storage is available to complete this operation. Compiler returned error 0x8007000e
A few more reference that might be useful for troubleshooting the issue:
Thanks for reading my rambling. I wrote this for my quick reference and to help people who is searching for similar information.
WARNING: Above steps involves re-registration of WMI providers. So, test them in your lab and try in production at your own risk. I am not responsible for any damage that caused by above procedure.
MS has released a free tool that can help you search PSTs available in your network and export them to Exchange 2010 archive mailboxes of users. This product is originally developed by red gate and MS has acquired this and released as a free tool to encourage its customers to move from PST to exchange archives.
For more details and downloads, refer to below links.
If you are windows administrator, you often come across a requirement to know when a service is started. The most obvious way is to drill through the event log and fetch this information. For this either you need to login to server interactively or verify event viewer remotely. Working interactively or via Event Viewer MMC is a painful process especially if the remote server is located in a remote place with high latency wan connection. I too came across similar situation and felt I should have somewhat easy to get this information. I quickly explored Win32_Service WMI class but I couldn’t find this service start information in any of the attributes/methods. After spending sometime, I came across a “PowerShell Guy” article which took me in right direction. The approach the author used in that post is very simple. Just get the PID of the process and get the process creation time which has that PID. It is matter of just two WMI calls. I found it is easy method and wrote the below script.
[cmdletbinding()]param([parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string[]]$ComputerName=$env:computername,[ValidateNotNullOrEmpty()][Alias("ServiceName")][string]$Name)begin{}Process{foreach($Computerin$ComputerName){if(Test-Connection-ComputerName$Computer-Count1-ea0){Write-Verbose"$Computer is online"$Service=Get-WmiObject-ClassWin32_Service-ComputerName$Computer-Filter"Name='$Name'"-ea0if($Service){$ServicePID=$Service.ProcessID$ProcessInfo=Get-WmiObject-ClassWin32_Process-ComputerName$Computer-Filter"ProcessID='$ServicePID'"-ea0$OutputObj=New-Object-TypePSObject$OutputObj|Add-Member-MemberTypeNoteProperty-NameComputerName-Value$Computer.ToUpper()$OutputObj|Add-Member-MemberTypeNoteProperty-NameName-Value$Name$OutputObj|Add-Member-MemberTypeNoteProperty-NameDisplayName-Value$Service.DisplayName$OutputObj|Add-Member-MemberTypeNoteProperty-NameStartTime-Value$($Service.ConvertToDateTime($ProcessInfo.CreationDate))$OutputObj}else{write-verbose"Service `($Name`) not found on $Computer"}}else{write-Verbose"$Computer is offline"}}}end{}
Sometimes we want to know when a computer is built. There are different places where we can check to find out the proximate information. Examples include, computer object creation datetime in active directory, build logs if you use any OS deployment softwares, creation date of c:\windows folder, and a WMI query if you are scripting geek etc. Since WMI provides most accurate information about when a Operating System is installed, I wrote a quick script to solve one of my requirement where I want to get installed date of bunch of computers.
Here is the script.
[cmdletbinding()]param([parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)][string[]]$ComputerName=$env:computername)begin{}process{foreach($Computerin$ComputerName){if(Test-Connection-ComputerName$Computer-Count1-ea0){Write-Verbose"$Computer is online"$OS=Get-WmiObject-ClassWin32_OperatingSystem-Computer$Computer$InstalledDate=$OS.ConvertToDateTime($OS.Installdate)$OutputObj=New-Object-TypePSObject$OutputObj|Add-Member-MemberTypeNoteProperty-NameComputerName-Value$Computer$OutputObj|Add-Member-MemberTypeNoteProperty-NameInstalledDate-Value$InstalledDate$OutputObj}else{Write-Verbose"$Computer is offline"}}}end{}
You can use this script in variety of ways. Below are the some of the usage examples. Save the above code into Get-InstallDate.ps1 file before you try them.
[PS] C:\>.\Get-InstalledDate.ps1 — get installed date of local computer
[PS] C:\>.\Get-InstalledDate.ps1 -ComputerName “MYtestpc1” — get installed date of local computer
[PS] C:\>”mytestpc1″, “mytestpc2” | .\Get-InstalledDate.ps1 — if you want to do it for multiple computers
[PS] C:\>Get-content .\comps.txt | .\Get-InstalledDate.ps1 — if you want to read the computers list from a text file and find out the information.