Remotely install ‘Win32_Product Class’ on Server 2003


In a previous post (PowerShell v2 Gather remote software inventory), I lamented the fact that Server 2003 does not include the Win32_Product class by default. (http://blogs.msdn.com/wmi/default.aspx)

I was asked to install it on our 500 Server 2003 boxes via a script.

I built a few test VM’s, and configured the Win32_Product class via “Add/Remove Windows Components”

Doing so, I found that the following three files are required by the “Management and Monitoring Tools”

MSI.MFL
MSI.MOF
MSIPROV.DLL

I copied the three (compressed) files from the AMD64 directory on my installation disk to my script folder.

From various sources on the Internet, I discovered I could install it from a command line as: Sysocmgr /I:\c:\windows\inf\sysoc.inf /u:<AnswerFile> where AnswerFile is:

[Components]
WbemMSI = On

My first attempts were to use the WMI class Win32_Process to run the command as

$process

= ([WMICLASS]"\\$server\ROOT\CIMV2:Win32_Process").Create(“Sysocmgr /I:\c:\windows\inf\sysoc.inf /u:\\mypc\c$\answerfile.txt”)

, but that was problematic, as sysocmgr didn’t seem to like having the answerfile on a remote path.

I copied the answerfile.txt to the remote computer, and ran the command, but it still didn’t work. Researching the issue on Bing, and looking in the log file  “C:\windows\setupapi.log”,  I realized the process was waiting for the CD to be inserted.

More time on Bing made me realize the the remote server registry would need to be manipulated for me to provide the files on an alternate path.

I figured the easiest way to provide the three files I needed was to copy them each remote server, modify the registry to point to that location, and run the command again. This almost worked. Now I got errors in the setupapi.log saying my files weren’t signed. These files came right off the CD, so I wasn’t about to try signing them, and I wasn’t about to believe they weren’t signed by Microsoft (if they truly needed to be).

I DID find out that I could expand them on the target machine, and then install them remotely, so I created a .bat file to do that. I also figured I might as well have the .bat file run the the sysocmgr line too.

 

Here is the .bat file that I copy to each remote server:

001
002
003
004
005
006
expand c:\wmiinstall\i386\msi.mf_ c:\windows\system32\wbem\msi.mfl
expand c:\wmiinstall\i386\msi.mo_ c:\windows\system32\wbem\msi.mof
expand c:\wmiinstall\i386\msiprov.dl_ c:\windows\system32\wbem\msiprov.dll
regsvr32 /s c:\windows\system32\wbem\msiprov.dll
Sysocmgr /I:\c:\windows\inf\sysoc.inf /u:c:\WMIInstall\AnswerFile.txt > c:\WMIInstall\install.txt
exit

 

Now, all I needed to do was run the batch file remotely after modifying the registry, and then modify the registry back.

Here is what I came up with:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
function Get-RegString{ 
    param( 
        [string]$server = ".", 
        [string]$hive, 
        [string]$keyName, 
        [string]$valueName, 
        [object]$defaultValue
    ) 
    $hives = [enum]::getnames([Microsoft.Win32.RegistryHive]) 
    if($hives -notcontains $hive){ 
        write-error "Invalid hive value"; 
        return; 
    } 
    $regHive = [Microsoft.Win32.RegistryHive]$hive; 
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($regHive,$server); 
    $subKey = $regKey.OpenSubKey($keyName); 

    if(!$subKey){ 
        write-error "The specified registry key does not exist."; 
        return; 
    } 
    $subKey.GetValue($valueName,$defaultValue); 
} 
function Set-RegString{ 
    param( 
        [string]$server = ".", 
        [string]$hive, 
        [string]$keyName, 
        [string]$valueName, 
        [string]$value   
    ) 
    $hives = [enum]::getnames([Microsoft.Win32.RegistryHive]) 

    if($hives -notcontains $hive){ 
        write-error "Invalid hive value"; 
        return; 
    } 
    $regHive = [Microsoft.Win32.RegistryHive]$hive; 
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($regHive,$server); 
    $subKey = $regKey.OpenSubKey($keyName,$true); 

    if(!$subKey){ 
        write-error "The specified registry key does not exist."; 
        return; 
    } 
    $subKey.SetValue($valueName, $value, [Microsoft.Win32.RegistryValueKind]::String); 
    if($?) {$true} else {$false} 
} 
function Get-RegDWord{ 
    param( 
        [string]$server = ".", 
        [string]$hive, 
        [string]$keyName, 
        [string]$valueName, 
        [object]$defaultValue="Your default value" 
    ) 

    $hives = [enum]::getnames([Microsoft.Win32.RegistryHive]) 

    if($hives -notcontains $hive){ 
        write-error "Invalid hive value"; 
        return; 
    } 
    $regHive = [Microsoft.Win32.RegistryHive]$hive; 
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($regHive,$server); 
    $subKey = $regKey.OpenSubKey($keyName); 

    if(!$subKey){ 
        write-error "The specified registry key does not exist."; 
        return; 
    } 
    $subKey.GetValue($valueName,$defaultValue); 
} 
function Set-RegDWord{ 
    param( 
        [string]$server = ".", 
        [string]$hive, 
        [string]$keyName, 
        [string]$valueName, 
        [double]$value   
    ) 

    $hives = [enum]::getnames([Microsoft.Win32.RegistryHive]) 

    if($hives -notcontains $hive){ 
        write-error "Invalid hive value"; 
        return; 
    } 
    $regHive = [Microsoft.Win32.RegistryHive]$hive; 
    $regKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($regHive,$server); 
    $subKey = $regKey.OpenSubKey($keyName,$true); 

    if(!$subKey){ 
        write-error "The specified registry key does not exist."; 
        return; 
    } 
    $subKey.SetValue($valueName, $value,[Microsoft.Win32.RegistryValueKind]::DWord); 
    if($?) {$true} else {$false} 
} 
function Test-Process{
param(
[string]$procid,
[string]$computer
)
  (get-wmiobject Win32_Process ProcessId -filter "ProcessId=$procid" -ComputerName $computer ) -ne $NULL
}
function Add-WmiProvidor{
foreach ($server in $servers)
{
#Find the registry keys for the original source path
. Get-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows nt\CurrentVersion"
$OriginalSource = $SubKey.GetValue("SourcePath")
. Get-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup"
$OriginalDrive = $SubKey.GetValue("SourcePath")
$OriginalServicePackSourcePath = $SubKey.GetValue("ServicePackSourcePath")
. Get-RegDWord $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup"
$OriginalCDInstall = $SubKey.GetValue("CDInstall")

#Set the registry keys to point to install directory
$NewSource = "c:\WMIInstall\"
$newDrive = "c:\WMIInstall"
$NewServicePackSourcePath = "c:\WMIInstall"
$NewCDInstall = 0

Write-Output "Setting registry on $server"
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows nt\CurrentVersion" SourcePath $NewSource |Out-Null
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" SourcePath $NewDrive |Out-Null
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" ServicePackSourcePath $NewServicePackSourcePath |Out-Null
Set-RegDWord $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" CDInstall $NewCDInstall |Out-Null

#Copy files needed for the install to $server\c$\temp
robocopy "c:\scripts\WMIInstall" "\\$server\c$\WMIInstall" /s |Out-Null
Write-Output "Copying files to $server"
#Execute the .bat file
$process = ([WMICLASS]"\\$server\ROOT\CIMV2:Win32_Process").Create("cmd.exe /c c:\WMIInstall\install.bat")
#Wait for the process to end:
[string]$ID = $process.ProcessID
Write-Output "Waiting for process $ID to finish on $server"
while (test-process $process.ProcessId $server) { }  # Wait for terminate
#Reset the registry keys
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows nt\CurrentVersion" SourcePath $OriginalSource |Out-Null
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" SourcePath $OriginalDrive |Out-Null
Set-RegString $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" ServicePackSourcePath $OriginalServicePackSourcePath |Out-Null
Set-RegDWord $server "localMachine" "SOFTWARE\Microsoft\Windows\CurrentVersion\Setup" CDInstall $OriginalCDInstall |Out-Null
Write-Output "Resetting registry on $server"
}
}
$servers = Get-Content servers.txt
Add-WmiProvidor

Once again, I rely heavily on Shay Levi’s registry functions. Get them here:
http://blogs.microsoft.co.il/blogs/scriptfanatic/archive/2007/10/30/stand-alone-registry-functions-library.aspx

The robocopy line on 132 copies the following files to the target C drive:

C:\WMIInstall\install.bat
C:\WMIInstall\answerfile.txt
C:\WMIInstall\I386\MSI.MF_
C:\WMIInstall\I386\MSI.MO_
C:\WMIInstall\I386\MSIPROV.DL_

 

I have tested this on both x86 and x64 versions of Server 2003 R2.

I found the Test-Process function somewhere, but I cannot remember where – if you know, please let me know, so I can credit the author!

Advertisements
  1. #1 by Starbuck on January 9, 2013 - 10:12

    Great post! I am wondering if those files you pulled off the CD are the same across various editions of 2003.

  2. #3 by Vishnu on April 5, 2013 - 19:42

    Thanks ! this did an awesome Job.

  1. The Binary Idiot » Finding MSI-Based Software with Powershell
  2. Finding MSI-Based Software with Powershell | The Binary Idiot

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: