Introduction
Normally Systems Management Server(SMS) Packages are created by using the SMS AdminstratorConsole. For automation purposes, sometimes a programmatic way is needed to create a package from a *.msi Installer file. The Systems Management Server 2003 Software Development Kit (see the Microsoft® SMS SDK) only supports the creation of an SMS package from a *.pdf file. To create a package from a *.msi file, a workaround is needed.
The idea is quite simple: reading the attributes out of the MSI database and adding them to a manually created SMS package.
To use the code in the demo project, just include a reference to msi.dll (usually found in system32). If you include it with Visual Studio (Project + Add Reference, COM tab, select "Microsoft Windows Installer Object Library") it generates a wrapper called Interop.WindowsInstaller.dll.
You also need a reference to Microsoft.SystemsManagementServer.Automation.dll which can be downloaded here.
If you download both files (Interop.WindowsInstaller.dll and Microsoft.SystemsManagementServer.Automation.dll) and copy them into the lib folder of the sample project, all references are OK because I set the references to this folder.
Using the Code
First we have to determine the attributes of the MSI database. To read the MSI database, we need to import the Microsoft® Windows Installer (see the Microsoft® Windows Installer Reference). It is defined as a COM import and has to be declared in the class:
[System.Runtime.InteropServices.ComImport(),
System.Runtime.InteropServices.Guid
("000C1090-0000-0000-C000-000000000046")]
class Installer { }
Now we are able to query the MSI file.
As described in other tutorials, we do this in these steps:
- Open the MSI database/file
- Query the database
- Store the Attributes in a
Hashtable
for further use
FileInfo msiFile = new FileInfo("c:\\MyPath\\MyInstallerFile.msi")
Hashtable msiData = new Hashtable();
WindowsInstaller.Installer inst = (WindowsInstaller.Installer)new Installer();
Database instDb = inst.OpenDatabase(msiFile.FullName,
WindowsInstaller.MsiOpenDatabaseMode.msiOpenDatabaseModeReadOnly);
WindowsInstaller.View view = instDb.OpenView
("Select `Property`,`Value` FROM `Property`");
view.Execute(null);
Record record = view.Fetch();
while (record != null)
{
Console.WriteLine(record.get_StringData(1)+"|"+record.get_StringData(2));
msiData.Add(record.get_StringData(1), record.get_StringData(2));
record = view.Fetch();
}
view.Close();
The next thing to do is to build the new SMS package with the data stored in the msiData
Hashtable
. This can be done with the following steps:
- Connect to the SMS server
- Create a new SMS package
- Add the attributes stored in
msiData
SMSProvider smsProvider = null
try
{
String smsServer="\\\\root\\sms\\site_";
smsProvider = new SMSProvider(smsServer, "myUserName", "myPassword");
}
catch(Exception ex)
{
Console.Error.WriteLine("Could not connect to the SMS Provider.",ex);
MessageBox.Show("Could not connect to the SMS Provider.");
}
SMSPackage smsPkg;
if(msiData.ContainsKey("ProductName"))
{
smsPkg = smsProvider.Packages.Create(msiData["ProductName"].ToString());
}
else
{
Console.Error.WriteLine("msi contains no product name.");
return null;
}
if(msiData.ContainsKey("ProductLanguage"))
{
smsPkg.Language = msiData["ProductLanguage"].ToString();
Console.WriteLine("set smsPkg.Language="+
msiData["ProductLanguage"].ToString());
}
smsPkg.MIFFileName = msiFile.Name;
Console.WriteLine("set smsPkg.MIFFileName="+ msiFile.Name);
smsPkg.PkgSourcePath = "c:\\temp\\sourcen";
Console.WriteLine("set smsPkg.PkgSourcePath="+ smsPkg.PkgSourcePath);
Now we have to create the install and uninstall programs. Despite a *.pdf file, the *.msi file does not contain the install or uninstall programs.
Therefore we will create them manually.
String name = "Installation (Service)";
String cmd = "msiexec.exe /q ALLUSERS=2 /i \""+msiFile.Name+"\"";
Console.WriteLine("creating install program: Name= "+name+" CMD= "+cmd);
SMSProgram inst = smsPkg.Programs.Create(name,cmd);
inst.RunWithAdminRights = true;
inst.UserRequirements = UserRequirementsFlags.RunWhether
gedOnOrOff;
inst.Save();
name = "Uninstall (Service)";
cmd = "msiexec.exe /q /x \""+msiFile.Name+"\"";
Console.WriteLine("creating install program: Name= "+name+" CMD= "+cmd);
SMSProgram deInst = smsPkg.Programs.Create(name,cmd);
deInst.RunWithAdminRights = true;
deInst.UserRequirements = UserRequirementsFlags.RunWhetherLoggedOnOrOff;
deInst.Save();
Last but not least we have to save the SMS package. The Save
method stores the changes we made in the SMS server.
smsPkg.Save();
Console.WriteLine("SMS package with id: "+smsPkg.PackageID+" created.")
This is quite a simple solution, but works fine. The Microsoft SMS toolkit is a big help while developing custom SMS solutions. Unfortunately the documentation is not so good and the support for *.msi files is missing.
Points of Interest
If you want to determine the Siteservername out of the registry, you can find it under:
- Microsoft.Win32.Registry.CurrentUser Software\Microsoft\SMS\Admin UI\MRU\Site1\ServerName
or - Microsoft.Win32.Registry.LocalMachine Software\Microsoft\SMS\AdminUI\Connection\Server
First try to get the Servername out of HKCU, then from HKLM.
The demo project demonstrates how to determine the server out of the registry and how to connect to it. If you have questions about the programmatic use of the SMS Server, please feel free to contact me.
References
Please feel free to browse the following reference material: