PowerShell Script to Install MSI Silently and Remotely
PowerShell Script to Install MSI Silently and Remotely
Blog

PowerShell Script to Install MSI Silently and Remotely

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

  1. Use HTTPS for PowerShell Remoting when possible
  2. Implement proper credential management – avoid hardcoded passwords
  3. Use Group Policy to configure PowerShell execution policies
  4. Audit installation activities through comprehensive logging
  5. Test installations in isolated environments first

Performance Optimization Tips

  1. Use parallel processing for multiple computers
  2. Implement throttling to avoid overwhelming networks
  3. Cache MSI files locally when possible
  4. Use background jobs for large deployments
  5. 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.

Leave a Response