Remote MSI install using c# and WMI

Hi,

I having a problem whilst trying to install an application on remote machines using WMI. The code im using seems right, but it just isn't working. Can anybody,please, help point me in the right direction? Or maybe post a link/script i can have a look at?

Just to note the strings..

_DOMAIN
_USERNAME
_PASSWORD
_MSIPATH
_ARGS
_MACHINE

are all set correctly before they are passed to the method...

Here is the code I'm working on...The thing is it doesn't throw any Exceptions at all, but it dosen't work either.

So i am at a bit of a loss as how to fix it...

Any help at all would be greatly appreciated.

CODE:
--

public void installMsi(string _DOMAIN, string _USERNAME, etc, etc)

{
ManagementScope ms = null;

// Connection options
ConnectionOptions co = new ConnectionOptions();
co.Impersonation = ImpersonationLevel.Impersonate;
co.Authentication = AuthenticationLevel.PacketPrivacy;
co.Authority = "ntlmdomain:" + _DOMAIN;

// local machine
if (_MACHINE.ToUpper() == Environment.MachineName.ToUpper())
{
ms = new ManagementScope(@"\ROOT\CIMV2", co);
}
// remote machine
else
{
co.Username = _USERNAME;
co.Password = _PASSWORD;
ms = new ManagementScope(@"\\" + _MACHINE + "\root\cimv2", co);
ms.Options.Impersonation = ImpersonationLevel.Impersonate;
}
try
{
ms.Connect();
ManagementPath mp = new ManagementPath("Win32_Product");
ObjectGetOptions ogo = new ObjectGetOptions();
ManagementClass mc = new ManagementClass(ms, mp, ogo);
ManagementBaseObject inParams = mc.GetMethodParameters("Install");
inParams["PackageLocation"] = _MSIPATH;
inParams["Options"] = _ARGS;
inParams["AllUsers"] = true;
ManagementBaseObject retVal = mc.InvokeMethod("Install", inParams, null);
}

catch (Exception ex)
{

MessageBox.Show("Exception: " + ex.Message);

}
catch (ManagementException err)
{
MessageBox.Show("WMI Error: " + err.Message);
}
catch (System.UnauthorizedAccessException unauthorizedErr)
{
MessageBox.Show("Connection error: " + unauthorizedErr.Message);
}

}

//END

Thanks.

Fraser

[2787 byte] By [fraser_foo] at [2007-12-26]
# 1

Hi, this part of code should help you.

Code depends on two things:

  • connection to local/remote computer.
  • computer is in domain or workgroup.

Local:

/// <summary>

/// Binds working namespace to local computer's root\cimv2 namespace.

/// </summary>

public void ConnectLocalComputer()

{

m_WorkingNamespace = new ManagementScope();

m_WorkingNamespace.Path = new ManagementPath(@"\\.\root\cimv2");

try

{

m_WorkingNamespace.Connect();

}

catch (COMException comException)

{

this.m_WorkingNamespace = null;

throw new ArgumentException("Server does not exists or access denied. \nConfigure firewall options.", comException);

}

catch (UnauthorizedAccessException authException)

{

this.m_WorkingNamespace = null;

throw new ArgumentException("Access denied or timeout expired. \nCheck if you are in domain Administrators group.", authException);

}

}

Remote:

/// <summary>

/// Binds working namespace to remote computer's root\cimv2 namespace.

/// </summary>

/// <param name="computerName">Name or ip address of remote computer.</param>

/// <param name="domainName">Remote computer's domain. If computer is not in domain pass null value.</param>

/// <param name="userName">Windows user name, domain account in the Administrators group.</param>

/// <param name="password">Username's password.</param>

/// <param name="connectionTimeout">Connection timeout.</param>

public void ConnectRemoteComputer( string computerName, string domainName, string userName, string password,

TimeSpan connectionTimeout )

{

ConnectionOptions connectionConfiguration = new ConnectionOptions();

connectionConfiguration.Impersonation = ImpersonationLevel.Impersonate;

connectionConfiguration.Authentication = AuthenticationLevel.Default;

if (domainName == null || domainName == string.Empty)

{

connectionConfiguration.Username = computerName + "\\" + userName;

connectionConfiguration.Authority = null;

}

else

{

connectionConfiguration.Username = userName;

connectionConfiguration.Authority = "NTLMdomain:" + domainName;

}

connectionConfiguration.Password = password;

connectionConfiguration.Timeout = connectionTimeout;

connectionConfiguration.EnablePrivileges = true;

m_WorkingNamespace = new ManagementScope();

m_WorkingNamespace.Path = new ManagementPath(@"\\" + computerName + @"\root\cimv2");

m_WorkingNamespace.Options = connectionConfiguration;

try

{

m_WorkingNamespace.Connect();

}

catch( COMException comException )

{

this.m_WorkingNamespace = null;

throw new ArgumentException("Server does not exists or access denied. \nCheck host name and configure firewall options.", comException);

}

catch( UnauthorizedAccessException authException )

{

this.m_WorkingNamespace = null;

throw new ArgumentException("Access denied or timeout expired. \nCheck if username, password and domain are correct and if user is a member of domain Administrators group.", authException);

}

}

Remember that you will connect remotely only if remote user is in Administrators group and your firewall has remote admnistration enabled.

Now installing:

/// <summary>

/// Installs new msi package on bound computer

/// </summary>

/// <param name="msiFilePath">Path (on bound computer) to msi package.

/// There can be only path to local, phisical drive!</param>

/// <param name="installOptions">Additional command line options for installation if format property=setting.</param>

/// <param name="allUsers">Indicates whether packege is installed for all users.</param>

public void InstallProduct(string msiFilePath, string installOptions, bool allUsers)

{

if (!this.IsConnected)

throw new InvalidOperationException("Object is not bound to WMI namespace. Use one of Connect methods before this operation.");

ManagementClass productClass = new ManagementClass(this.m_WorkingNamespace,

new ManagementPath("Win32_Product"), new ObjectGetOptions());

try

{

object[] parameters = { msiFilePath, installOptions, allUsers };

UInt32 returnValue = (UInt32) productClass.InvokeMethod("Install", parameters);

if (returnValue > 0)

throw new Exception("Installation failed. Error code = " + returnValue);

}

catch (ManagementException exc)

{

throw new Exception("Installation failed. RPC Server Fault Error.", exc);

}

}

I hope it helps :)

Cheers,

Marek

GrabarzM at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 2

Hi Marek,

Wow, thanks so much for your response, it has helped me a great deal.

With regard to the remote installation of MSI packages, I eventually write a method which allowed me to use MSI packages hosted on a third machine.

Computer A: machine I’m working on

Computer B: machine I’m want to install apps on

Computer C: machine hosting the MSI packages.

The additional steps were to setup Kerberos delegation in Active Directory.

I.E:

1: Enabled delegation in Active Directory on the domain controller.

2: The account on B marked as Trusted (Kerberos).

3: The account on A not marked as sensitive.

I decided to post the code in case anyone else was looking for a similar answer. The code is a bit sloppy but it did what I wanted. Hope it may help someone.

public void RemoteMSI(string machine, string msi, string commandline, string username, string password, string domain)

{

try

{

ConnectionOptions connection =

new ConnectionOptions();

connection.Authority = "kerberos:" + domain + @"\" + machine;

connection.Username = username;

connection.Password = password;

connection.Impersonation = ImpersonationLevel.Delegate;

connection.Authentication = AuthenticationLevel.PacketPrivacy;

//define the WMI root name space

ManagementScope scope =

new ManagementScope(@"\\" + machine + "." + domain + @"\root\CIMV2", connection);

//define path for the WMI class

ManagementPath p =

new ManagementPath("Win32_Product");

//define new instance

ManagementClass classInstance = new ManagementClass(scope, p, null);

// Obtain in-parameters for the method

ManagementBaseObject inParams = classInstance.GetMethodParameters("Install");

// Add the input parameters.

inParams["AllUsers"] = true; //to install for all users

inParams["Options"] = commandline; //paramters must be in the format “property=setting“

inParams["PackageLocation"] = msi; //source file must be on the remote machine

// Execute the method and obtain the return values.

ManagementBaseObject outParams = classInstance.InvokeMethod("Install", inParams, null);

// List outParams

string retVal = outParams["ReturnValue"].ToString();

string msg = null;

switch (retVal)

{

case "0":

msg = "The installation completed successfully.";

break;

case "2":

msg = "The system cannot find the specified file. \n\r\n\r" + msi;

break;

case "3":

msg = "The system cannot find the path specified. \n\r\n\r" + msi;

break;

case "1619":

msg = "This installation package \n\r\n\r " + msi + "\n\r\n\rcould not be opened, please verify that it is accessible.";

break;

case "1620":

msg = "This installation package \n\r\n\r " + msi + "\n\r\n\rcould not be opened, please verify that it is a valid MSI package.";

break;

default:

msg = "Please see... \n\r\n\r http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/error_codes.asp \n\r\n\rError code: " + retVal;

break;

}

// Display outParams

MessageBox.Show(msg, "Installation report");

}

catch (ManagementException me)

{

MessageBox.Show(me.Message, "Management Exception");

}

catch (COMException ioe)

{

MessageBox.Show(ioe.Message, "COM Exception");

}

}

fraser_foo at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 3

Hi fraser_foo,

I've used the same code in .NET 2.0, but i'm facing installer file acess perfmission error # 1619.

Please give me a solution for this.

Regards,

Udooz

udooz at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 4

Hi Udooz,

Well Error # 1619 is ERROR_INSTALL_PACKAGE_OPEN_FAILED if you check on:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/msi/setup/error_codes.asp

You can see that the decription for this is:

This installation package could not be opened. Verify that the package exists and is accessible, or contact the application vendor to verify that this is a valid Windows Installer package.

So at a guess the MSI isn't accessable to the target machine...Are your paths correct?

Also, when you say you used the same code, what do you mean by that? The code I posted above?

I wrote that code to work in a particular application i was developing. It may not be what you need at all...

lIf you post your code i could propbably give some more help...

F.

fraser_foo at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 5

Hi Fraser_Foo,

Consider the following case:

Machine A: The application is running.

Machine B: where the MSI file (eg: TestAppSetup.MSI) is placed.

Machine C: target machine on which i need to install TestAppSetup.MSI

S.No

Scenario

Test Result

Return Value

1

Running application in A.

MSI file in B. [Accessing this in UNC file path format]

Target machine is A itself with local credential.

PASS

0

2

Running application in A.

MSI file in B.[Accessing this in UNC file path format]

Target machine is C with admin credential.

FAIL

1619

3

Running application in A.

MSI file in C. [Accessing this in UNC file path format]

Target machine is C with admin credential.

PASS

0

But my application exactly needs the scenario #2 only.

See the sample code:

string machineCName; //In this case, "machineC"

if
(!Directory.Exists(installerPath)) //installerPath eg. \\machineB\builds
{
return "The source installer path " + installerPath + " is invalid";
}

machineCFullPath = "\\\\" + machineCName + @"\root\cimv2";

ManagementScope ms = null;

ConnectionOptions co = new ConnectionOptions();

co.Impersonation = ImpersonationLevel.Impersonate;

co.Authentication = AuthenticationLevel.Default;

co.Authority = "kerberos:" + domainName + "\\" + machineCName;

co.Username = mUserName;

co.Password = mPassword;

// New

co.EnablePrivileges = true;

ms = new ManagementScope(machineCFullPath, co);

try

{

ms.Connect();

}

catch (System.Runtime.InteropServices.COMException comException)

{

return "Server does not exists or access denied. Check host name and configure firewall options. Details: " + comException.Message

+ Environment.NewLine + comException.StackTrace;

}

catch (UnauthorizedAccessException authException)

{

return "Access denied or timeout expired. Check if username, password and domain are correct and if user is a member of domain Administrators group. Details: " +

authException.Message + Environment.NewLine + authException.StackTrace;

}

catch (Exception ex)

{

return "Unexpected error occurred. Details: " + ex.Message + Environment.NewLine + ex.StackTrace;

}

try

{

string[] msiFiles = Directory.GetFiles(installerPath, "*.msi");

int intRetVal = 0;

string lastMSIFile = "";

for (int i = 0; i < msiFiles.Length; i++)

{

lastMSIFile = msiFilesIdea;

ManagementPath mp = new ManagementPath("Win32_Product");

ObjectGetOptions ogo = new ObjectGetOptions();

ManagementClass mc = new ManagementClass(ms, mp, ogo);

ManagementBaseObject inParams = mc.GetMethodParameters("Install");

inParams["PackageLocation"] = msiFilesIdea;

if (mArgument != null && mArgument.Trim() != "")

inParams["Options"] = mArgument;

inParams["AllUsers"] = true;

ManagementBaseObject retVal = mc.InvokeMethod("Install", inParams, null);

intRetVal = Convert.ToInt32(retVal["ReturnValue"]);

if (intRetVal != 0)

break;

}

if (intRetVal == 1619)

return "Unable to open installation package " + lastMSIFile + ". Check the file permission.";

else if (intRetVal == 1620)

return "Unable to open installation package " + lastMSIFile + ". Verify that given is valid MSI.";

else if (intRetVal == 2)

return "The system cannot find the MSI file.";

else if (intRetVal == 3)

return "The system cannot find installer path.";

else if (intRetVal == 1259)

return "The MSI " + lastMSIFile + " is incompatible with current operating system installed on this machine.";

else if (intRetVal == 1601)

return "Unable to access MSI service in this machine.";

else if (intRetVal == 1618)

return "An installer already running on the machine.";

else if (intRetVal != 0)

return "Unknown error code " + intRetVal + " returned.";

return "";

}

catch (Exception ex)

{

return "Error while performing installation. Details: " + ex.Message + " " + ex.StackTrace;

}

Please send reply asap. Thanks

Regards,

Udooz

udooz at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 6

Hi Udooz,

I would suggest that the two lines in you code:

co.Impersonation = ImpersonationLevel.Impersonate;

co.Authentication = AuthenticationLevel.Default;

need to read:

co.Impersonation = ImpersonationLevel.Delegate;

co.Authentication = AuthenticationLevel.PacketPrivacy;

Also you need to enable delegation on the computer accounts.

Have a look at the following:


A: Machine running your application.

B: Machine to install software on to.

C: Machine hosting the required MSI packages

+-+ +-+ +-+
| | | | | |
| A: | ==== 1st hop ===> | B: | ==== 2nd hop ===> | C: |
| | (A connects to B) | | (B connects to C) | |
+-+ +-+ +-+

1: Enable delegation in Active Directory on the domain controller.

2: The account on B must be marked as Trusted (Kerberos).

3: The account on A must not be marked as sensitive.

4: A, B, and the domain controller must be members of the same domain.

Working Code:

// set these values to your requirements

_DOMAIN = "your.domain"; // your domain name

_MACHINE = "target.machine"; // machine to install on

_USERNAME ="username"; // username of account that exists on A and B

_PASSWORD = "password"; // password for _USERNAME account

_MSI = "\\path\to\package.msi"; // UNC path to the MSI package

_ARGS = "property=value"; // commandline arguments for installer

//

ConnectionOptions co =

new ConnectionOptions();

co.Authority = "kerberos:" + _DOMAIN + @"\" + _MACHINE;

co.Username = _USERNAME;

co.Password = _PASSWORD;

//to impersonate current logged user

co.Impersonation = ImpersonationLevel.Delegate;

//PRC_C_IMP_LEVEL_DELEGATE (c++)

co.Authentication = AuthenticationLevel.PacketPrivacy;

co.EnablePrivileges = true;

//define the WMI root name space

ManagementScope ms =

new ManagementScope(@"\\" + _MACHINE + "." + _DOMAIN + @"\root\CIMV2", co);

//define path for the WMI class

ManagementPath mp =

new ManagementPath("Win32_Product");

//define new instance

ManagementClass mc =

new ManagementClass(ms, mp, null);

// Obtain input parameters for the method

ManagementBaseObject inParams = mc.GetMethodParameters("Install");

// Add the input parameters.

inParams["AllUsers"] = true; //to install for all users

inParams["PackageLocation"] = _MSI;//msi source file

inParams["Options"] = _ARGS;//'property=setting'

// Execute the method and obtain the return values.

ManagementBaseObject outParams =

mc.InvokeMethod("Install", inParams, null);

fraser_foo at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 7

Hi fraser_foo

I've done the same, but still, i can't resolved.

If i'm using NTLMADMIN. getting the "Not Found" error at the following line:

ManagementBaseObject inParams = objMgmtClass.GetMethodParameters("Install");

Exception Detail:

Message: "Not found "

Source: "System.Management"
StackTrace: at System.Management.ManagementException.ThrowWithExtendedInfo(ManagementStatus errorCode)\r\n at System.Management.ManagementObject.Initialize(Boolean getObject)\r\n at System.Management.ManagementObject.get_ClassPath()\r\n at System.Management.ManagementObject.GetMethodParameters(String methodName, ManagementBaseObject& inParameters, IWbemClassObjectFreeThreaded& inParametersClass, IWbemClassObjectFreeThreaded& outParametersClass)\r\n at System.Management.ManagementObject.GetMethodParameters(String methodName)\r\n at iSOFT.iDeployer.App.Deploy.DeployMSI(String serverIP, String installerPath) in D:\\Projects\\PackDeploy\\iDeployerSolution\\iDeployer\\Deploy.cs:line 160"

If I'm using Kerberos, getting the "A security package specific error occurred. (Exception from HRESULT: 0x80070721)" error at the same:

Exception Detail:

Source: "mscorlib"

StackTrace: " at System.Runtime.InteropServices.Marshal.ThrowExceptionForHRInternal(Int32 errorCode, IntPtr errorInfo)\r\n at System.Management.ManagementObject.Initialize(Boolean getObject)\r\n at System.Management.ManagementObject.get_ClassPath()\r\n at System.Management.ManagementObject.GetMethodParameters(String methodName, ManagementBaseObject& inParameters, IWbemClassObjectFreeThreaded& inParametersClass, IWbemClassObjectFreeThreaded& outParametersClass)\r\n at System.Management.ManagementObject.GetMethodParameters(String methodName)\r\n at iSOFT.iDeployer.App.Deploy.DeployMSI(String serverIP, String installerPath) in D:\\Projects\\PackDeploy\\iDeployerSolution\\iDeployer\\Deploy.cs:line 160" string

Please help me.

Regards,

Udooz

udooz at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 8

Hi Udooz,

You can't do this 2-hop-method via NTLMADMIN, you must use Kerberos delegation. The error your getting is because the client asks for delegation, but the created security context does not support delegation.

Do you have a firewall runnig on the server? on the target machine? If so try turning these off... If it then works, you need to configure the firewall to stop it blocking the requests.

Also, are the machines all at least XP? or is the target 2000?

F.

fraser_foo at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 9

Hi, fraser_foo,

I am using your code, but always got an exception of "The RPC server is unavailable". In my situation, I want to deploy a msi package to computer B remotely. B is the target machine, A is the machine running the application code. Also, the msi package is in a shared directory in machine A. A is windows 2003 server, B is Windows XP SP2. I am using a domain administrator account. Any ideas about the exception?

Thanks

John

johnzhou at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 10

The error message: RPC Server is Unavailable

This issue can occur for any of the following reasons:

?The RPC service may not be started.
?You are unable to resolve a DNS or NetBIOS name.
?An RPC channel cannot be established.

You could try:

1) Make sure the Remote Procedure Call (RPC) service is running on both machines (net start rpcss)

2) Make sure the Remote Procedure Call Locator (rpclocator) service is running on both machines (net start rpclocator)

3) Make sure the DCOM Server Process Launcher (DcomLaunch) service is running on both machines (net start dcomlaunch)

4) Make sure any firewall / antivirus software isn't blocking the RPC.

Regards,

Fraser

fraser_foo at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 11

Thank you very much for your response. I checked all items you mentioned above and found no problem. But I still got "RPC server is unavailable" error. I read your previous posted message carefully and found you mentioned in one of the messages the following:

The additional steps were to setup Kerberos delegation in Active Directory.

I.E:

1: Enabled delegation in Active Directory on the domain controller.

2: The account on B marked as Trusted (Kerberos).

3: The account on A not marked as sensitive.

What's the detailed steps to do it (I am using domain administrator's account)?

Thanks

John

jzhou415 at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 12

Hi Fraser,

I've did all those things.

After that, it returns error number 1619 or 2, even i've provided all permissions to access the installer file path.

Regards,

Udooz

udooz at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 13

Hi, Marek,

I am using your code to install a MSI package on remote machines (I am using the code you posted exactly and the MSI package is already on the remote machine). It works fine if the remote machine is a windows xp sp2. But It fails if remote machine is windows 2003 server sp1 and it throws an ManagementException. Do you have any idea about this?

Thanks

John

jzhou415 at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...
# 14

Hi John,

Check that whether WMI Installer Provider is installed on your server or not? By default, WMI Windows Installer Provider is not installed in Win2003. You have to install by

select Start > Control Panel > Add or Remove Programs.

In Add or Remove Programs window, press Add or Remove Windows Components button.

In Windows Components Wizard window, select Management and Monitoring Tools from the Components list. Press Details.. button.

In Management and Monitoring Tools window, select WMI Windows Installer Provider in the Subcomponents of Management and Monitoring Tools list.

Press OK.

And one more thing, did u check the following scenario?

Place the MSI file on one machine and install it on some other remote machine through WMI. If yes, please give me the details.

Regards,

Udooz

udooz at 2007-9-4 > top of Msdn Tech,.NET Development,.NET Base Class Library...

.NET Development

Site Classified