InstallRemoteMSI.psm1

<#
.Synopsis
   Install a MSI file on a remote machine using PowerShell.
 
.DESCRIPTION
   This script installs a MSI file remotly on a Windows based
   machine using PowerShell remoting. The files are first copied
   to the remote machine and executed there. After the installation
   all remote files are cleaned up. Logging is done with every
   step of the process. Log files can be located in the local
   C:\Windows\Temp folder.
 
.Parameter MSIFile
    File name of the MSI file. If no filename is ommited
    "install.msi" is used.
 
.Parameter MSILocalWorkingDirectory
    The Name of the local install folder. E.g. C:\Foldername.
    Please note that the MSIFile should be present in this
    folder.
 
.Parameter MSIParam
    Parameters used during the installation of the MSI file. When
    this parameter is not ommited the default of "/quiet" is used.
 
.Parameter ComputerListPath
    The full path to the file containing the host names of the target
    machines.
 
.Parameter OpenLog
    Opens the logfile after an installation.
 
.Parameter NoClear
    Does not clear the screen when using -Verbose combined with -Openlog
     
.Example
    Install-MSI-RemotePS.ps1 -MSIFile "LAPS.X64.msi" -MSILocalWorkingDirectory "C:\temp\install" -ComputerListPath "C:\temp\servers.txt"
 
    This example installs the MSI with the name "LAPS.X64.msi" located in the local foler "C:\temp\install" to all the machines found
    in the file "C:\temp\servers.txt". The "LAPS.X64.msi" file is installed with the default parameter "/quiet"
#>



Function Install-RemoteMSI{

[cmdletbinding(
        DefaultParameterSetName='default'
)]

Param(
    [parameter(
        Position=0, 
        Mandatory = $true,
        ParameterSetName='default'
    )]
    [String]$MSILocalWorkingDirectory,

    [parameter(
        Position=1,
        Mandatory = $true,
        ParameterSetName='default'
    )]
    [String]$MSIFile="install.msi",
 
    [parameter(
        Mandatory=$false,
        ParameterSetName='default'
    )]
    [String]$MSIParam="/quiet",

    [parameter(
        Mandatory = $true,
        ParameterSetName='default'
    )]
    [String]$ComputerListPath,

    [parameter(
        Mandatory = $false,
        ParameterSetName='default'
    )]
    [switch]$OpenLog,

    [parameter(
        Mandatory = $false,
        ParameterSetName='default'
    )]
    [switch]$NoClear

)


$MSIRemoteWorkingDirectory = "C:\Windows\temp\$((New-Guid).Guid)"
$LogFile = ("$env:windir\temp\$(get-date -f yyyy-MM-dd-hh-mm-ss).txt")
$Tab = [char]9
$start = get-date

If(!(Test-Path (Join-Path $MSILocalWorkingDirectory $MSIFile))){
    Write-Error "Installer File does not exist, installation cannot continue" -ErrorAction Stop
} 

If(!(Test-Path $ComputerListPath)){
    Write-Error "Server list does not exist, installation cannot continue" -ErrorAction Stop
} Else 
{
    $ComputerNames = Get-Content -Path $ComputerListPath | where {$_.trim() -ne "" }
}

cls

foreach($ComputerName in $ComputerNames){

    Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Installing on: $($ComputerName)"
    Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Starting on $($($ComputerName).ToUpper()) " | Out-File $LogFile -Append

    $ping = $null
    $Ping = Get-WmiObject -Class Win32_PingStatus -Filter "Address='$ComputerName' AND Timeout=1000"


if ($Ping.IPV4Address){

Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Machine $($($ComputerName).ToUpper()) Is Available" | Out-File $LogFile -Append

    Try
    {
        Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Creating session"
        $session = New-PSSession -ComputerName $computerName -ErrorAction SilentlyContinue    
                
        If(!($session)){
            Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Session could not be created"
            throw $error[0].ErrorDetails
        } Else {
            Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Session with id $($session.ID) to $($($ComputerName).ToUpper()) Succesfully created" | Out-File $LogFile -Append        
        }
        
        Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Copy installation files"
        Copy-Item -Path $MSILocalWorkingDirectory -Filter * -ToSession $session -Destination $MSIRemoteWorkingDirectory -Recurse -Force -ErrorVariable CopyFile -ErrorAction SilentlyContinue
        
        If($CopyFile){
            Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Installation files could not be copied"
            throw $error[0].ErrorDetails
        } Else {
            Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Installation files to $($($ComputerName).ToUpper()) succesfully copied" | Out-File $LogFile -Append
        }

        Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Installing on remote host"
        $MSIProcessExitCode = Invoke-Command -Session $session -ScriptBlock {
            $MSIProcess = Start-Process msiexec.exe -ArgumentList "/I $($args[1]) $($args[2])" -WorkingDirectory $($args[0]) -Wait -PassThru
            return $MSIProcess.ExitCode

            Remove-Item -Path $args[0] -Recurse -Force

        } -ArgumentList $MSIRemoteWorkingDirectory, $MSIFile, $MSIParam -ErrorAction SilentlyContinue
        
        If($MSIProcessExitCode -ne 0)
        {
            Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Installation was not successfull"
            Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Error MSI Error Code: $MSIProcessExitCode" | Out-File $LogFile -Append
            throw
        } Else {
            Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info $MSIFile on $($($ComputerName).ToUpper()) Succesfully Installed" | Out-File $LogFile -Append
        }

        Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Remove the session"
        Remove-PSSession $session

        If(!($Session.Availability -eq "None")){ 
           Write-Verbose -Message "$(get-date -f "hh:mm:ss:ms") $($Tab) Session could not be terminated"
           throw $error[0].ErrorDetails
        } Else {
            Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Session ID $($Session.ID) to Machine $($($ComputerName).ToUpper()) succesfully terminated" | Out-File $LogFile -Append
        }

        Write-Verbose "$(get-date -f "hh:mm:ss:ms") $($Tab) $($($ComputerName).ToUpper()) succesfully installed"
        Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Succes $MSIFile on $($($ComputerName).ToUpper()) Installed" | Out-File $LogFile -Append
     } 
    
    Catch {  
        Write-Host "$($($ComputerName).ToUpper()) Was not succesfully installed, please review the log" -ForegroundColor Red
        Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Error $($error[0].FullyQualifiedErrorId)" | Out-File $LogFile -Append
    }

} Else
  {
    Write-Host "$($($ComputerName).ToUpper()) can not be located or reached" -ForegroundColor Red
    Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Error $($($ComputerName).ToUpper()) could not be located or reached" | Out-File $LogFile -Append
  }
} 

$end = get-date
$TimeSpan = New-TimeSpan -Start $start -End $end
Write-Verbose -Message ""
Write-Verbose -Message "Total runtime was $([math]::Round($($TimeSpan.TotalSeconds),3)) seconds"
Write-Output "$(Get-Date -Format "yyyy-MM-dd hh:mm:ss"), Info Total runtime was $([math]::Round($($TimeSpan.TotalSeconds),3)) seconds" | Out-File $LogFile -Append

If($OpenLog)
{
    If(!($NoClear)){
        cls
    } Else {
        write-host ""
    }

    If(Test-Path $LogFile) {
        $Logcontent = Get-Content $LogFile
        
        Foreach($Line in $Logcontent) {
            If($line -like "*, Succes*") {
                Write-Host $Line -ForegroundColor Green
            } ElseIf ($line -like "*, Error*") {
                Write-Host $Line -ForegroundColor Red
            } Else {
                Write-Host $Line
            }
        }              
    }
}

}