Installing MSI packages silently and remotely using PowerShell is a critical skill for system administrators managing multiple Windows machines. This comprehensive guide provides tested scripts, best practices, and troubleshooting tips for automating MSI installations across your network infrastructure.
What is Silent MSI Installation?
Silent MSI installation allows you to deploy software packages without user interaction, making it ideal for automated deployments, scheduled installations, and mass software rollouts. PowerShell provides robust methods to execute these installations both locally and on remote systems.
Prerequisites for Remote MSI Installation
Before implementing remote MSI installations, ensure your environment meets these requirements:
- PowerShell Remoting enabled on target machines
- Administrative privileges on both local and remote systems
- Network connectivity between machines
- Windows Installer service running on target systems
- Firewall exceptions for PowerShell remoting (typically port 5985 for HTTP, 5986 for HTTPS)
Basic PowerShell Script for Silent MSI Installation
Local Silent Installation
# Basic local MSI installation script
$MSIPath = "C:\Installers\YourSoftware.msi"
$LogPath = "C:\Logs\Installation.log"
# Silent installation with logging
Start-Process -FilePath "msiexec.exe" -ArgumentList "/i `"$MSIPath`" /quiet /l*v `"$LogPath`"" -Wait -NoNewWindow
Write-Host "Installation completed. Check log at: $LogPath"
Advanced Local Installation with Error Handling
function Install-MSISilently {
param(
[Parameter(Mandatory=$true)]
[string]$MSIPath,
[Parameter(Mandatory=$false)]
[string]$LogPath = "C:\Logs\MSI_Install_$(Get-Date -Format 'yyyyMMdd_HHmmss').log",
[Parameter(Mandatory=$false)]
[hashtable]$Properties = @{}
)
# Validate MSI file exists
if (-not (Test-Path $MSIPath)) {
throw "MSI file not found at: $MSIPath"
}
# Create log directory if it doesn't exist
$LogDir = Split-Path $LogPath -Parent
if (-not (Test-Path $LogDir)) {
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
}
# Build property string
$PropertyString = ""
if ($Properties.Count -gt 0) {
$PropertyString = ($Properties.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join " "
}
# Build argument list
$Arguments = "/i `"$MSIPath`" /quiet /l*v `"$LogPath`" $PropertyString"
try {
Write-Host "Starting MSI installation..."
Write-Host "MSI Path: $MSIPath"
Write-Host "Log Path: $LogPath"
$Process = Start-Process -FilePath "msiexec.exe" -ArgumentList $Arguments -Wait -NoNewWindow -PassThru
if ($Process.ExitCode -eq 0) {
Write-Host "Installation completed successfully!" -ForegroundColor Green
return $true
} else {
Write-Error "Installation failed with exit code: $($Process.ExitCode)"
Write-Host "Check the log file for details: $LogPath" -ForegroundColor Yellow
return $false
}
}
catch {
Write-Error "Error during installation: $($_.Exception.Message)"
return $false
}
}
# Usage example
$InstallProperties = @{
"INSTALLDIR" = "C:\Program Files\YourApp"
"ALLUSERS" = "1"
}
Install-MSISilently -MSIPath "C:\Installers\YourSoftware.msi" -Properties $InstallProperties
Remote MSI Installation Scripts
Single Remote Computer Installation
function Install-MSIRemotely {
param(
[Parameter(Mandatory=$true)]
[string]$ComputerName,
[Parameter(Mandatory=$true)]
[string]$MSIPath,
[Parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]$Credential,
[Parameter(Mandatory=$false)]
[hashtable]$Properties = @{}
)
$ScriptBlock = {
param($MSIPath, $Properties)
# Create log path on remote machine
$LogPath = "C:\Logs\Remote_MSI_Install_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$LogDir = Split-Path $LogPath -Parent
if (-not (Test-Path $LogDir)) {
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
}
# Build property string
$PropertyString = ""
if ($Properties.Count -gt 0) {
$PropertyString = ($Properties.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join " "
}
# Build arguments
$Arguments = "/i `"$MSIPath`" /quiet /l*v `"$LogPath`" $PropertyString"
try {
$Process = Start-Process -FilePath "msiexec.exe" -ArgumentList $Arguments -Wait -NoNewWindow -PassThru
return @{
ExitCode = $Process.ExitCode
LogPath = $LogPath
Success = ($Process.ExitCode -eq 0)
ComputerName = $env:COMPUTERNAME
}
}
catch {
return @{
ExitCode = -1
LogPath = $LogPath
Success = $false
Error = $_.Exception.Message
ComputerName = $env:COMPUTERNAME
}
}
}
try {
$SessionParams = @{
ComputerName = $ComputerName
}
if ($Credential) {
$SessionParams.Credential = $Credential
}
Write-Host "Installing MSI on $ComputerName..." -ForegroundColor Yellow
$Result = Invoke-Command @SessionParams -ScriptBlock $ScriptBlock -ArgumentList $MSIPath, $Properties
if ($Result.Success) {
Write-Host "Successfully installed on $($Result.ComputerName)" -ForegroundColor Green
} else {
Write-Warning "Installation failed on $($Result.ComputerName). Exit code: $($Result.ExitCode)"
if ($Result.Error) {
Write-Warning "Error: $($Result.Error)"
}
}
return $Result
}
catch {
Write-Error "Failed to connect to $ComputerName : $($_.Exception.Message)"
return $null
}
}
# Usage example
$Creds = Get-Credential
Install-MSIRemotely -ComputerName "SERVER01" -MSIPath "\\FileServer\Software\YourApp.msi" -Credential $Creds
Multiple Remote Computers Installation
function Install-MSIOnMultipleComputers {
param(
[Parameter(Mandatory=$true)]
[string[]]$ComputerNames,
[Parameter(Mandatory=$true)]
[string]$MSIPath,
[Parameter(Mandatory=$false)]
[System.Management.Automation.PSCredential]$Credential,
[Parameter(Mandatory=$false)]
[hashtable]$Properties = @{},
[Parameter(Mandatory=$false)]
[int]$ThrottleLimit = 10
)
$ScriptBlock = {
param($MSIPath, $Properties, $ComputerName)
$LogPath = "C:\Logs\Remote_MSI_Install_$(Get-Date -Format 'yyyyMMdd_HHmmss').log"
$LogDir = Split-Path $LogPath -Parent
if (-not (Test-Path $LogDir)) {
New-Item -ItemType Directory -Path $LogDir -Force | Out-Null
}
$PropertyString = ""
if ($Properties.Count -gt 0) {
$PropertyString = ($Properties.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" }) -join " "
}
$Arguments = "/i `"$MSIPath`" /quiet /l*v `"$LogPath`" $PropertyString"
try {
$Process = Start-Process -FilePath "msiexec.exe" -ArgumentList $Arguments -Wait -NoNewWindow -PassThru
return [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
ExitCode = $Process.ExitCode
Success = ($Process.ExitCode -eq 0)
LogPath = $LogPath
InstallTime = Get-Date
Error = $null
}
}
catch {
return [PSCustomObject]@{
ComputerName = $env:COMPUTERNAME
ExitCode = -1
Success = $false
LogPath = $LogPath
InstallTime = Get-Date
Error = $_.Exception.Message
}
}
}
$Jobs = @()
foreach ($Computer in $ComputerNames) {
$SessionParams = @{
ComputerName = $Computer
ScriptBlock = $ScriptBlock
ArgumentList = $MSIPath, $Properties, $Computer
AsJob = $true
JobName = "MSIInstall_$Computer"
}
if ($Credential) {
$SessionParams.Credential = $Credential
}
Write-Host "Starting installation job for $Computer..." -ForegroundColor Yellow
try {
$Job = Invoke-Command @SessionParams
$Jobs += $Job
}
catch {
Write-Warning "Failed to start job for $Computer : $($_.Exception.Message)"
}
# Throttle job creation
while ((Get-Job -State Running).Count -ge $ThrottleLimit) {
Start-Sleep -Seconds 2
}
}
# Wait for all jobs to complete and collect results
Write-Host "Waiting for all installation jobs to complete..." -ForegroundColor Yellow
$Results = @()
foreach ($Job in $Jobs) {
$JobResult = Wait-Job $Job | Receive-Job
$Results += $JobResult
Remove-Job $Job
}
# Display summary
Write-Host "`n=== Installation Summary ===" -ForegroundColor Cyan
$Successful = $Results | Where-Object { $_.Success }
$Failed = $Results | Where-Object { -not $_.Success }
Write-Host "Successful installations: $($Successful.Count)" -ForegroundColor Green
Write-Host "Failed installations: $($Failed.Count)" -ForegroundColor Red
if ($Failed.Count -gt 0) {
Write-Host "`nFailed Computers:" -ForegroundColor Red
$Failed | ForEach-Object {
Write-Host " $($_.ComputerName) - Exit Code: $($_.ExitCode)" -ForegroundColor Red
if ($_.Error) {
Write-Host " Error: $($_.Error)" -ForegroundColor Red
}
}
}
return $Results
}
# Usage example
$ComputerList = @("SERVER01", "SERVER02", "WORKSTATION01", "WORKSTATION02")
$Credentials = Get-Credential
$InstallProps = @{
"INSTALLDIR" = "C:\Program Files\YourApp"
"ALLUSERS" = "1"
}
$Results = Install-MSIOnMultipleComputers -ComputerNames $ComputerList -MSIPath "\\FileServer\Software\YourApp.msi" -Credential $Credentials -Properties $InstallProps
Common MSI Installation Parameters
Understanding MSI command-line parameters is crucial for successful silent installations:
Parameter | Description | Example |
---|---|---|
/i |
Install package | msiexec /i package.msi |
/quiet |
Quiet mode (no user interaction) | msiexec /i package.msi /quiet |
/passive |
Unattended mode (progress bar only) | msiexec /i package.msi /passive |
/l*v |
Verbose logging | msiexec /i package.msi /l*v log.txt |
/norestart |
Suppress restart | msiexec /i package.msi /norestart |
PROPERTY=VALUE |
Set MSI properties | msiexec /i package.msi INSTALLDIR="C:\MyApp" |
Best Practices for Remote MSI Installation
1. Network Share Considerations
- Use UNC paths accessible by all target machines
- Ensure proper share permissions
- Consider using DFS for redundancy
2. Credential Management
# Secure credential storage example
$SecurePassword = ConvertTo-SecureString "YourPassword" -AsPlainText -Force
$Credential = New-Object System.Management.Automation.PSCredential("Domain\Username", $SecurePassword)
3. Error Handling and Logging
# Enhanced logging function
function Write-InstallLog {
param(
[string]$Message,
[ValidateSet("Info", "Warning", "Error")]
[string]$Level = "Info"
)
$Timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
$LogEntry = "[$Timestamp] [$Level] $Message"
switch ($Level) {
"Info" { Write-Host $LogEntry -ForegroundColor Green }
"Warning" { Write-Warning $LogEntry }
"Error" { Write-Error $LogEntry }
}
# Also write to file
$LogEntry | Out-File -FilePath "C:\Logs\PowerShell_MSI_Install.log" -Append
}
4. Pre-Installation Checks
function Test-RemoteInstallationReadiness {
param([string]$ComputerName, [string]$MSIPath)
$Checks = @{
"Computer Online" = Test-Connection -ComputerName $ComputerName -Count 1 -Quiet
"PowerShell Remoting" = Test-WSMan -ComputerName $ComputerName -ErrorAction SilentlyContinue
"MSI File Accessible" = Test-Path $MSIPath
}
return $Checks
}
Troubleshooting Common Issues
MSI Exit Codes
- 0 – Success
- 1603 – Fatal error during installation
- 1618 – Another installation is in progress
- 1622 – Error opening installation log file
- 1633 – This installation package is not supported by this processor type
PowerShell Remoting Issues
# Enable PowerShell Remoting on remote computers
Enable-PSRemoting -Force
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "*" -Force
Firewall Configuration
# Configure Windows Firewall for PowerShell Remoting
New-NetFirewallRule -DisplayName "PowerShell Remoting" -Direction Inbound -LocalPort 5985 -Protocol TCP -Action Allow
Security Considerations
- Use HTTPS for PowerShell Remoting when possible
- Implement proper credential management – avoid hardcoded passwords
- Use Group Policy to configure PowerShell execution policies
- Audit installation activities through comprehensive logging
- Test installations in isolated environments first
Performance Optimization Tips
- Use parallel processing for multiple computers
- Implement throttling to avoid overwhelming networks
- Cache MSI files locally when possible
- Use background jobs for large deployments
- Monitor system resources during installations
Conclusion
PowerShell provides powerful capabilities for silent and remote MSI installations. The scripts and best practices outlined in this guide will help you automate software deployments efficiently while maintaining security and reliability. Always test installations in development environments before deploying to production systems.
Remember to customize the scripts according to your specific requirements, including proper error handling, logging, and security measures appropriate for your environment.
A big thank you for exploring TechsBucket! Your visit means a great deal to us, and we appreciate the time you spend on our platform. If you have any feedback or suggestions, we’d love to hear them.