Automating printer configuration for the Modern Workplace

Helloooooo! Wow – I just realised how long it has been since my last post! Time flies when you’re involved in multiple projects and attempting to take on, and hopefully be okay at, a team lead role. 😎 Like many in IT this year, we have been as busy as, if not slightly busier than the average bunch of beavers, or even bees for that matter! No complaints here though – we love our jobs! 😃😁😆

I am always on the lookout for a wee (or what may turn into a ‘not so wee’) project to challenge my PowerShell skills, and after recent discussions around issues with local printer deployments to Modern Workplace endpoints using Intune, I thought – that’s my jam!

Without a print server onsite, it is logical to deploy direct IP connections to printers where possible, or to opt for a cloud print solution. There are many cloud print options around in 2023; they do potentially add complexity and cost however and may not be required for a small to mid-sized business with relatively basic printing requirements. They are also another component of the environment that may fail or introduce security concerns etc.

However, cloud-based printing solutions are a must when using swipe cards or reporting on usage. If you have a lease agreement for your printing, talk to the vendor; most of them have a cloud solution to offer you. As more small and medium-sized businesses shift to cloud-based app and configurations, the solutions will become smarter and more cost effective, I’m sure.

Anyway… to business! The main challenges with automating the creation of local IP printers are:

  • They can default to color output and/or printing on one or both sides of the paper.
  • Some drivers present a security prompt even when installing as ‘System’, preventing silent installation.
  • They may have advanced properties e.g. stock or label sizes. With no standards for development these settings could be stored in the registry or a file somewhere in the user profile. It can be tricky to identify where the settings are kept.

Hopefully we can fix the first two of these possibilities with the scripts below (which you can contribute to here on GitHub if you like!). And for the last one we’ll go through some common places to find things with an example using SATO label printers.

Pre-requisite tasks:

  1. Download all the print drivers you need in your deployment and extract them to a folder (let’s call it the Root or Source Folder). Also copy the PrintInst4intune.ps1 script into this folder.
  2. You will need to know the names of the drivers you are installing, to put into an input file. You can get the names from within the INF files in the driver folders you downloaded, but more likely from wherever they were installed before under the Advanced tab of the printer properties. In the INF file you can look for something like below where it might be the “SATO LC408e” we need to install.

3. Now create an input file in Excel following the example below… This must have all the DriverName values 100% correct. Save this as a TAB delimited file in the root folder. The script looks for printerdetails.txt by default.

4. Great! We have all we need – time to run the script! I spent a bit of time putting in checks and logging so you should be able to run it multiple times without it erroring out due to things already existing etc.

In a nutshell, here’s what PrintInst4intune.ps1 does:

  1. Restarts the spooler service (just to make sure there are no issues that could prevent success).
  2. Searches the root folder to get a list of CER files, then adds them to the Local Machine Trusted Root and Trusted Publisher stores. There are two reasons a driver may pop up with a security prompt; A) the cert it was signed with is not trusted OR B) the cert it was signed with has expired. Installing the cert into the Local Machine cert store will fix occurrence A, so we do that first. If you have scenario B, you will more likely need to contact the vendor for an updated driver.
  3. Searches the root folder to get a list of INF files.
  4. Uses Pnputil.exe to import the drivers into the Windows driver store.
  5. Uses Add-PrinterDriver to install the drivers we need based on our input file.
  6. Uses the info from the input file to create the printer ports. (there are still no usable printers at this point, but you would see the drivers and ports in the Print Management GUI).

In most environments you could use this to deploy one package containing all the required drivers and ports for your environment, to all devices, using Intune device targeting (or similar using any RMM or device management tool).

You now have all drivers and ports available on all workstations. Keep reading below for the second script to actually deploy the printers to your users.

Here is the device script – but check GitHub for the latest revision! Cheers, Simon. 👌😁👌

# start

# Installs any driver signing certificates (cer) and print drivers (inf) found within a source folder
# Extract all print drivers into the same folder as the script - run in system context.

# run with Administrative rights, or for Intune deploy in System context to groups of users

$rootfolder     = "c:\temp\"                                      # for intune use $rootfolder = ".\" 
$inputfile      = "$rootfolder" + "PrinterDetails.txt"       # tab delimited file containing columns 'printername' 'drivername' 'ipaddress' 'port' 'location'
# moved to PrintInst4intune-user.ps1 ==> $colour         = $false         # default to black only, set to $true for colour
# moved to PrintInst4intune-user.ps1 ==> $duplex         = "Onesided"     # default to one-sided, can be set to TwoSidedLongEdge or TwoSidedShortEdge
$filterinf      = "*.inf"                                         # the files in the root folder structure that enable the import and installation of printer drivers 
$filtercer      = "*.cer"                                         # the certificate files that may exist in the source structure
$certstoreroot  = "Cert:\LocalMachine\Root"                       # the local machine root certificate store to which cer files are imported
$certstoretrusted  = "Cert:\LocalMachine\TrustedPublisher"        # the local machine trusted publisher certificate store to which cer files are imported
$logfile        = "$rootfolder" + "_PrintInst4intune.log"         # for intune use $logfile = "$env:TEMP" + "\_printinst4intune.log"
$warningcount   = 0                                               # any actions that fail increment this counter

$null = Start-Transcript -Path $logfile

$x = 300
Do {
    Restart-Service Spooler
    Start-Sleep -Seconds 1
    $x = $x - 1
    if ($x -eq 0) { 
        $null = Stop-Transcript
        throw "Spooler did not restart!"
    }
} until (Get-Service Spooler | Where-Object { $_.Status -eq "Running" })

if (-not(Test-Path -Path $rootfolder -ErrorAction Ignore)) {
    $null = Stop-Transcript
    throw "The root folder $rootfolder does not exist!"
}
if (-not(Test-Path -Path $inputfile -ErrorAction Ignore)) {
    $null = Stop-Transcript
    throw "The input file $inputfile does not exist!"
}

$cerdetail  = Get-ChildItem -Path $rootfolder -Filter $filtercer -File -Recurse
$infdetail  = Get-ChildItem -Path $rootfolder -Filter $filterinf -File -Recurse

if ($null -eq $cerdetail) {
    Write-Output "No certificates found in $rootfolder..."
    Write-Output "***"
} else {
    Clear-Host
    Write-Output "" 
    Write-Output "Installing certificates found in $rootfolder..."
    Write-Output "" 
    
    foreach ($cer in $cerdetail) {
        $cerpath   = $cer.FullName
        Import-Certificate -FilePath $cerpath -CertStoreLocation $certstoreroot
        Import-Certificate -FilePath $cerpath -CertStoreLocation $certstoretrusted
    }    
}

if ($null -eq $infdetail) {
    $null = Stop-Transcript
    throw "No INF files found in $rootfolder... exiting."
} else {
    Clear-Host
    Write-Output "" 
    Write-Output "Importing drivers found in $rootfolder..."
    Write-Output "" 
    
    foreach ($inf in $infdetail) {
        $infpath = $inf.FullName
    
    Start-Process "Pnputil.exe" -ArgumentList "/add-driver $infpath /install" -Wait
        if ($? -ne "True") {
            Write-Warning "!!! Failed to import drivers from $infpath"
            Write-Output "***"
            $warningcount = $warningcount + 1
        } else {
            Write-Output "Successfully imported drivers from $infpath"
            Write-Output "***"
          }
    }    
}    

Clear-Host
Write-Output ""
Write-Output "Adding drivers and ports specified in $inputfile..."
Write-Output ""

# import input file
$printerdetails = Import-Csv $inputfile -Delimiter "`t" 

foreach ($printer in $printerdetails) {
    $printername    = $printer.PrinterName
    $drivername     = $printer.DriverName
    $ipaddress      = $printer.IPAddress
    $port           = $printer.Port

Add-PrinterDriver -Name $drivername
    if ($? -ne "True") {    
        Write-Warning "!!! Failed to add $drivername driver for $printername"
        Write-Output "***"
        $warningcount = $warningcount + 1
    } else {
        Write-Output "Successfully added $drivername driver for $printername"
        Write-Output "***"
      }

if (-not(Get-PrinterPort -Name "tcpip_$ipaddress" -ErrorAction Ignore)) { 
    Add-PrinterPort -Name "tcpip_$ipaddress" -PrinterHostAddress $ipaddress -PortNumber $port
        if ($? -ne "True") {    
            Write-Warning "!!! Failed to add tcpip_$ipaddress port for $printername"
            Write-Output "***"
            $warningcount = $warningcount + 1
        } else {
            Write-Output "Successfully added tcpip_$ipaddress port for $printername"
            Write-Output "***"
          }
} else {
    Write-Output "Port tcpip_$ipaddress exists... skipping"
    Write-Output "***"
  }
}

$null = Stop-Transcript
Clear-Host

if ($warningcount -eq 0) {
    Write-Output "***"
    Write-Output "No errors occurred - drivers and ports added successfully"
    Write-Output "Please Note: this script has NOT added any printers to the user session. Use PrintInst4intune-user.ps1 to deploy printers to users."
    Write-Output "Log file saved to $logfile."
    Write-Output "***"
} else {
    Write-Output "***"
    Write-Output "There were $warningcount errors - please review $logfile"
    Write-Output "***"
  }

# end

Okay! You’ve deployed all drivers and ports to all workstations. Now we can use PrintInst4intune-user.ps1 to deploy the printers to different sets of users as required.

In a nutshell, here’s what PrintInst4intune-user.ps1 does:

  1. Uses the input file to add the listed printers to the user session.
  2. Sets default printer properties to grey-scale and one-sided.
  3. Sets the default printer. The script uses an example of using the location value of AKL to set the default, but you have many options here:
  • You can remove this section if users are okay with selecting their own default.
  • You could add the name of a specific printer to be set as default (e.g. you can deploy to a site of users that all use the same default.).
  • You could add a column to iterations of the input file to set a different default printer for different groups of users e.g., add a ‘Default’ column and enter ‘Yes’ next to the one to set as default, then change the script to the below and deploy to a group containing the users you want to have this as their default printer:
# Set default printer

if ($printer.Default -eq "Yes") {
    $defaultprinter = Get-CimInstance -Class Win32_Printer -Filter "Name='$printername'"
    Invoke-CimMethod -InputObject $defaultprinter -MethodName SetDefaultPrinter
        if ($? -ne "True") {    
            Write-Warning "!!! Failed to set $printername as default"
            Write-Output "***"
            $warningcount = $warningcount + 1
    } else {
        Write-Output "Success setting $printername as default"
        Write-Output "***"
      }
}

Here is the user script – but please check GitHub for the latest revision! Cheers, Simon. 👌😁👌

# Add printers, set properties and default printer

$rootfolder     = "c:\temp\"                                # for intune or other deployment software use $rootfolder = ".\"
Set-Location $rootfolder
$inputfile      = $rootfolder + "PrinterDetails.txt"        # tab delimited file containing columns 'printername' 'drivername' 'ipaddress' 'port' 'location'
$colour         = $false                                    # $false for greyscale, $true for colour
$duplex         = "Onesided"                                # default to one-sided, can be set to TwoSidedLongEdge or TwoSidedShortEdge
$logfile        = "$rootfolder" + "_PrintInst4intune.log"   # for intune use $logfile = "$env:TEMP" + "\_PrintInst4intune.log"
$warningcount   = 0                                         # count of failed commands

$null = Start-Transcript $logfile -Force

$printerdetails = Import-Csv $inputfile -Delimiter "`t" 

foreach ($printer in $printerdetails) {
    $printername    = $printer.PrinterName
    $drivername     = $printer.DriverName
    $ipaddress      = $printer.IPAddress

    $location       = $printer.location

if (-not(Get-Printer -Name $printername -ErrorAction Ignore)) {
    Add-Printer -Name $printername -DriverName $drivername -PortName "tcpip_$ipaddress" -Comment "$ipaddress - $drivername" -Location $location
        if ($? -ne "True") {    
            Write-Warning "Failed to add $printername"
            Write-Output "***"
            $warningcount = $warningcount + 1
        } else {
            Write-Output "Success adding $printername"
            Write-Output "***"
          }
} else {
    Write-Output "Printer $printername exists... skipping"
    Write-Output "***"
  }

Set-PrintConfiguration -PrinterName $printername -Color $colour -DuplexingMode $duplex
    if ($? -ne "True") {    
        Write-Warning "!!! Failed to set properties for $printername"
        Write-Output "***"
        $warningcount = $warningcount + 1
    } else {
        Write-Output "Success setting properties for $printername"
        Write-Output "***"
      }
    }
# Set default printer

# if deploying with groups, you may have two queues for the printer 
# i.e. one to add the printer and another to add the printer and make it default. 
# In this situation we can set the default printer using only the Class and Invoke-CimMethod lines without an if statement.
# or we could use an if statement using the name or location or another column from the input file. e.g. HR,Sales,Accounts or AKL,WLG,CHC or Default with Yes value for the desired printer. 
# Add column(s) and data to the input file to cater for your scenario.
if ($printername -eq "AKL-Default") {
    $defaultprinter = Get-CimInstance -Class Win32_Printer -Filter "Name='$printername'"
    Invoke-CimMethod -InputObject $defaultprinter -MethodName SetDefaultPrinter
        if ($? -ne "True") {    
            Write-Warning "!!! Failed to set $printername as default"
            Write-Output "***"
            $warningcount = $warningcount + 1
    } else {
        Write-Output "Success setting $printername as default"
        Write-Output "***"
      }
}
# }

$null = Stop-Transcript
Clear-Host

if ($warningcount -eq 0) {
    Write-Output ""
    Write-Output "No errors occurred - printers added successfully"
    Write-Output "Log file saved to $logfile." 
} else {
    Write-Output ""
    Write-Output "There were $warningcount errors - please review $logfile"
  }

# end

As usual thanks for reading and I hope to see you again soon! 😜😜😜

Loading

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top