Install-PrinterDriversRemotely.ps1

<#PSScriptInfo
 
.VERSION 1.0
 
.GUID ede5f6c5-50d3-42a9-b958-daff4b31972d
 
.AUTHOR Juan Granados
 
.COPYRIGHT 2021 Juan Granados
 
.TAGS Install Printer Drivers Remote Remotely PrintNightmare PrinterExport
 
.LICENSEURI https://raw.githubusercontent.com/juangranados/powershell-scripts/main/LICENSE
 
.PROJECTURI https://github.com/juangranados/powershell-scripts/tree/main/Install%20Print%20Drivers%20Remotely
 
.RELEASENOTES
    Initial release
#>


<#
.SYNOPSIS
    Install printer drivers export file (*.printerExport) in a list of computers.
.DESCRIPTION
    Install printer drivers export file (*.printerExport) in a list of computers (plain computer list, OU or CSV file) using PSExec.
    To generate a printer export file with all print drivers, run PrintbrmUI.exe from the computer or server you want to export them.
    Be carefully because this tool exports all printers and ports too. I recommend install all drivers in a test computer without printers and export them using PrintbrmUI.exe.
    Alternatively, you can export all from print server, import in a test computer, delete printers and ports and export again to obtain a printerExport file with only drivers.
    If PSExec is not found on computer, script asks to the user for download it and extract in system folder.
    I recommend use PSExec latest version because v2.2 does not launch printbrm.exe properly.
.PARAMETER printerExportFile
    Path to the printerExport file
    To generate a printer export file with all print drivers, run PrintbrmUI.exe from the computer or server you want to export them.
    Be carefully because this tool exports all printers and ports too. I recommend install all drivers in a test computer without printers and export them using PrintbrmUI.exe.
    Alternatively, you can export all from print server, import in a test computer, delete printers and ports and export again to obtain a printerExport file with only drivers.
.PARAMETER ComputerList
    List of computers in install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV.
    Example: SRV-RDSH-001,SRV-RDSH-002,SRV-RDSH-003 (Without quotation marks)
.PARAMETER OU
    OU containing computers in which install printer drivers.
    RSAT for AD module for PowerShell must be installed in order to query AD.
     - Install on Windows 10: Get-WindowsCapability -Online |? {$_.Name -like "*RSAT.ActiveDirectory*" -and $_.State -eq "NotPresent"} | Add-WindowsCapability -Online
     - Install on server: Install-WindowsFeature RSAT-AD-PowerShell
    Restart console after installation.
    If you run script from a Domain Controller, AD module for PowerShell is already enabled.
    You can only use one source of target computers: ComputerList, OU or CSV.
    Example: 'OU=Test,OU=Computers,DC=CONTOSO,DC=COM'
.PARAMETER CSV
    CSV file containing computers in which install printer drivers. You can only use one source of target computers: ComputerList, OU or CSV.
    Example: 'C:\Scripts\Computers.csv'
    CSV Format:
        Name
        Computer001
        Computer002
        Computer003
.PARAMETER LogPath
    Path where save log file.
    Default: My Documents
    Example: C:\Logs
.PARAMETER Credential
    Script will ask for an account to perform remote installation.
.EXAMPLE
    Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -OU "OU=RDS,OU=Datacenter,DC=CONTOSO,DC=COM"
.EXAMPLE
    Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -ComputerList SRVRSH-001,SRVRSH-002,SRVRSH-003 -Credential -LogPath C:\Temp\Logs
.EXAMPLE
    Install-PrinterExportRemoteComputers.ps1 -printerExportFile "\\MV-SRV-PR01\Drivers\print_drivers.printerExport" -CSV "C:\scripts\computers.csv"
.LINK
    https://github.com/juangranados/powershell-scripts/tree/main/Install%20Print%20Drivers%20Remotely
.NOTES
    Author: Juan Granados
#>


Param(
        [Parameter(Mandatory=$true,Position=0)] 
        [ValidateNotNullOrEmpty()]
        [string]$printerExportFile,
        [Parameter(Mandatory=$false,Position=1)] 
        [ValidateNotNullOrEmpty()]
        [string]$LocalPath="C:\temp",
        [Parameter(Mandatory=$false,Position=2)] 
        [ValidateNotNullOrEmpty()]
        [string[]]$ComputerList,
        [Parameter(Mandatory=$false,Position=3)] 
        [ValidateNotNullOrEmpty()]
        [string]$OU,
        [Parameter(Mandatory=$false,Position=4)] 
        [ValidateNotNullOrEmpty()]
        [string]$CSV,
        [Parameter(Mandatory=$false,Position=5)] 
        [ValidateNotNullOrEmpty()]
        [string]$LogPath=[Environment]::GetFolderPath("MyDocuments"),
        [Parameter(Position=6)] 
        [switch]$Credential
    )

#Requires -RunAsAdministrator

#Functions

Add-Type -AssemblyName System.IO.Compression.FileSystem
Import-Module BitsTransfer

function Unzip
{
    param([string]$zipfile, [string]$outpath)
    
    [System.IO.Compression.ZipFile]::ExtractToDirectory($zipfile, $outpath)
}

$ErrorActionPreference = "Stop"

#Initialice log

$LogPath += "\InstallPrinterExportRemoteComputers_" + $(get-date -Format "yyyy-mm-dd_hh-mm-ss") + ".txt"
Start-Transcript $LogPath
Write-Host "Start remote installation on $(get-date -Format "yyyy-mm-dd hh:mm:ss")"

#Initial validations.

If (!(Test-Path $printerExportFile)){
    Write-Host "Error accessing $($printerExportFile). Script can not continue"
    Stop-Transcript
    Exit 1
}
if (-not($printerExportFile -Like "*.printerExport")) {
    Write-Host "File $($printerExportFile) does not has .printerExport extension. Script can not continue"
    Stop-Transcript
    Exit 1
}

if (!(Get-Command "psexec.exe" -ErrorAction SilentlyContinue)){ 
    Write-Host "Error. Microsoft Psexec not found on system. Download it from https://download.sysinternals.com/files/PSTools.zip and extract all in C:\Windows\System32" -ForegroundColor Yellow
    $Answer=Read-Host "Do you want to download and install PSTools (y/n)?"
    if (($Answer -eq "y") -or ($Answer -eq "Y")){
        Write-Host "Downloading PSTools"
        If (Test-Path "$($env:temp)\PSTools.zip"){
            Remove-Item "$($env:temp)\PSTools.zip" -Force
        }
        [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
        (New-Object System.Net.WebClient).DownloadFile("https://download.sysinternals.com/files/PSTools.zip", "$($env:temp)\PSTools.zip")
        if (Test-Path "$($env:temp)\PSTools.zip"){
            Write-Host "Unzipping PSTools"
            If (Test-Path "$($env:temp)\PSTools"){
                Remove-Item "$($env:temp)\PSTools" -Force -Recurse
            }
            Unzip "$($env:temp)\PSTools.zip" "$($env:temp)\PSTools" 
            Copy-Item "$($env:temp)\PSTools\*.exe" "$($env:SystemRoot)\System32" -Force
            if (Test-Path "$($env:SystemRoot)\System32\psexec.exe"){
                Write-Host "PSTools installed" -ForegroundColor Green 
            }
            else{
                Write-Host "Error unzipping PSTools" -ForegroundColor Red
                Remove-Item "$($env:temp)\PSTools.zip" -Force
                Stop-Transcript
                Exit 1
            }
        }
        else{
            Write-Host "Error downloading PSTools" -ForegroundColor Red
            Stop-Transcript
            Exit 1
        }
    }
    else{
        Stop-Transcript
        Exit 1
    }
}

If ($OU){
    if (!(Get-Command "Get-ADComputer" -ErrorAction SilentlyContinue)){ 
        Write-Host "Error. Get-ADComputer not found on system. You have to install the PowerShell Active Directory module order to query Active Directory." -ForegroundColor Red
        Write-Host 'Windows 10: Get-WindowsCapability -Online |? {$_.Name -like "*RSAT.ActiveDirectory*" -and $_.State -eq "NotPresent"} | Add-WindowsCapability -Online'
        Write-Host "Server: Install-WindowsFeature RSAT-AD-PowerShell"
        Write-Host "Restart console after installation"
        Stop-Transcript
        Exit 1
    }
    try{
        $ComputerList = Get-ADComputer -Filter * -SearchBase "$OU" | Select-Object -Expand name
    }catch{
        Write-Host "Error querying AD: $($_.Exception.Message)" -ForegroundColor Red
        Stop-Transcript
        Exit 1
    }
}
ElseIf ($CSV){
    try{
        $ComputerList = Get-Content $CSV | where {$_ -notmatch 'Name'} | Foreach-Object {$_ -replace '"', ''}
    }catch{
        Write-Host "Error getting CSV content: $($_.Exception.Message)" -ForegroundColor Red
        Stop-Transcript
        Exit 1
    }
}
ElseIf(!$ComputerList){
    Write-Host "You have to set a list of computers, OU or CSV." -ForegroundColor Red
    Stop-Transcript
    Exit 1
}
If ($Credential){
    $Cred = Get-Credential
}
$usingCredential = $false
If(!$Cred -or !$Credential){
    Write-Host "No credential specified. Using logon account"
}
Else{
    $usingCredential = $true;
    Write-Host "Using user $($Cred.UserName)"
    $UserName = $Cred.UserName
    $Password = $Cred.GetNetworkCredential().Password
}
ForEach ($Computer in $ComputerList) {
    Write-Host "Processing computer $Computer"
    $Destination = "\\$Computer\C$\Temp\printer_drivers.printerExport"
    try {
    if (-not(Test-Path "\\$Computer\C$\Temp\")) {
        mkdir "\\$Computer\C$\Temp\"
    }
    Start-BitsTransfer -Source $printerExportFile -Destination $Destination -Description "Copy $driverFile to $Computer" -DisplayName "Copying"
    } catch {
        Write-Host "Error copying file: $($_.Exception.Message)"
        continue
    }
    Write-Host "Launching installation using PSExec in $Computer. This may take a while, please be patient..."
    try {
        if ($usingCredential) {
            psexec.exe /accepteula -h -i "\\$Computer" -u $UserName -p $Password C:\Windows\System32\spool\tools\Printbrm.exe -F C:\Temp\printer_drivers.printerExport -R
        }
        else {
            psexec /accepteula -h "\\$Computer" C:\Windows\System32\spool\tools\Printbrm.exe -F C:\Temp\printer_drivers.printerExport -R
        }
    } catch {
        Write-Host "PSExec return an error. Check console output above"
    }

    try {
        Write-Host "Removing remote file"
        Remove-Item $Destination -Force;
    } catch {
        Write-Host "Error removing remote file: $($_.Exception.Message)" -ForegroundColor Red
    }
}     
Stop-Transcript