DSCClassResources/Printer/Printer.psm1
enum Ensure { Absent Present } enum PortType { TCPIP LPR PaperCut } [DscResource()] class Printer { [DscProperty(Mandatory)] [Ensure] $Ensure [DscProperty(Key)] [ValidateNotNullOrEmpty()] [System.String] $Name [DscProperty(Mandatory)] [System.String] $PortName [DscProperty()] [PortType] $PortType = [PortType]::TCPIP [DscProperty(Mandatory)] [System.String] $Address [DscProperty(Mandatory)] [System.String] $DriverName [DscProperty()] [System.Boolean] $Shared = $true [DscProperty()] [System.String] $PermissionSDDL [DscProperty()] [System.String] $SNMPCommunity [DscProperty()] [System.UInt32] $SNMPIndex = 0 [DscProperty()] [System.String] $lprQueueName hidden $Messages = "" Printer() { $this.Messages = Import-LocalizedData -FileName 'Printer.strings.ps1' -BaseDirectory (Split-Path -Parent $PSCOMMANDPATH) } [void] Set() { try { $printer = Get-Printer -Name $this.Name -Full -ErrorAction Stop } catch { Write-Verbose -Message ($this.Messages.PrinterDoesNotExist -f $this.Name) $printer = $null } # end try/catch try { $printerPort = Get-PrinterPort -Name $this.PortName -ErrorAction Stop } catch { Write-Verbose -Message ($this.Messages.PrinterPortDoesNotExist -f $this.PortName) $printerPort = $null } # end try/catch if ($this.Ensure -eq [Ensure]::Present) { <# Creating variables to determine if new a new printer or printerPort was just created. Doing this to bypass excess setting checks as the settings would already set correctly #> [bool]$newPrinter = $false [bool]$newPrinterPort = $false # We need to create the port before we can create the printer if ($null -eq $printerPort) { <# Different parameters are needed depending on the port type. So we are going to build a hashtable for splatting on the Add-PrinterPort cmdlet #> $addPrinterPortParams = @{ } switch ($this.PortType) { 'PaperCut' { $this.CreatePaperCutPort() } # End PaperCut 'LPR' { $addPrinterPortParams = @{ Name = $this.PortName LprHostAddress = $this.Address LprQueueName = $this.lprQueueName LprByteCounting = $true } } # End LPR Default { # Default is as Standard TCPIP Port $addPrinterPortParams = @{ Name = $this.PortName PrinterHostAddress = $this.Address } } # End Default } # End Switch PortType if ($addPrinterPortParams.Count -ge 1 ) { if ($this.SNMPIndex -ne 0 -and -not [string]::IsNullOrEmpty($this.SNMPCommunity)) { $addPrinterPortParams.SNMP = $this.SNMPIndex $addPrinterPortParams.SNMPCommunity = $this.SNMPCommunity } Add-PrinterPort @addPrinterPortParams } # end if port params count $newPrinterPort = $true Write-Verbose -Message ($this.Messages.NewPrinterPort -f $this.PortType, $this.PortName) } # End If PrinterPort if ($null -eq $printer) { try { Get-PrinterDriver -Name $this.DriverName -ErrorAction Stop } catch { Write-Error -Message ($this.Messages.PrinterNoDriver -f $this.DriverName, $this.Name) -Exception 'ObjectNotFound' -Category "ObjectNotFound" throw ($this.Messages.PrinterNoDriver -f $this.DriverName, $this.Name) } # end try/catch <# Building a hashtable with desired parameters of the new printer. This hashtable will be used for splatting the Add-Printer cmdlet. #> $addPrinterParam = @{ Name = $this.Name PortName = $this.PortName DriverName = $this.DriverName } if ($null -ne $this.PermissionSDDL) { $addPrinterParam.PermissionSDDL = $this.PermissionSDDL } # End If PermissionSDDL if ($null -ne $this.Shared) { $addPrinterParam.Shared = $this.Shared } # end if shared try { Add-Printer @addPrinterParam -ErrorAction Stop } catch { Write-Error -Message ($this.Messages.FailedToAddPrinter -f $this.Name) throw ($this.Messages.FailedToAddPrinter -f $this.Name) } # end try/catch $newPrinter = $true Write-Verbose -Message ($this.Messages.NewPrinter -f $this.Name) } # End If Printer # If the printer already existed the settings need to be checked. Otherwise the printer was just created with specified settings if ($newPrinter -eq $false) { <# Building a hashtable with the settings that needed to be changed. We do this so all changes are done with one command instead of running it each time a setting needs to get updated. #> $updatePrinterParam = @{ Name = $this.Name } if ($printer.DriverName -ne $this.DriverName) { # Need to check if the driver exists before attempting to set the printer to use it try { Get-PrinterDriver -Name $this.DriverName -ErrorAction Stop } catch { Write-Error -Message ($this.Messages.PrinterNoDriver -f $this.DriverName, $this.Name) -Exception 'ObjectNotFound' -Category "ObjectNotFound" throw ($this.Messages.PrinterNoDriver -f $this.DriverName, $this.Name) } # end try/catch # Updating variable to notify that the driver needs to be updated $updatePrinterParam.DriverName = $this.DriverName Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f 'DriverName', $this.DriverName, $printer.DriverName) } # End If DriverName if ($printer.Shared -ne $this.Shared) { $updatePrinterParam.Shared = $this.Shared Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f 'Shared', $this.Shared, $printer.Shared) } # End If Shared if ($null -ne $this.PermissionSDDL -and $printer.PermissionSDDL -ne $this.PermissionSDDL) { $updatePrinterParam.PermissionSDDL = $this.PermissionSDDL Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f 'PermissionSDDL', $this.PermissionSDDL, $printer.PermissionSDDL) } # End If PermissionSDDL if ($printer.PortName -ne $this.PortName) { $updatePrinterParam.PortName = $this.PortName Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f 'PortName', $this.PortName, $printer.PortName) # To make changes we need to make sure there are no jobs queued up on the printer Get-PrintJob -PrinterName $this.Name | Remove-PrintJob } # End If PrinterPort <# If the no params are added besides the default name property. We do not need to update the printer, otherwise the printer needs to be updated #> if ($updatePrinterParam.count -gt 1) { Set-Printer @updatePrinterParam } # End If updatePrinterParam } # End If NewPrinter # If the printerPort already existed the settings need to be checked. Otherwise the printer was just created with specified settings if ($newPrinterPort -eq $false) { $currentPortType = [PortType]$this.FindPortType() if ($currentPortType -ne $this.PortType) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "PortType", $currentPortType, $this.PortType) # If there are any PrintJobs queued on the printer it will cause issues changing the PortType so we will remove the print jobs Get-PrintJob -PrinterName $this.Name | Remove-PrintJob switch ($currentPortType) { 'PaperCut' { $tempPort = $this.UseTempPort() # Lets remove the Papercut Port Remove-Item ("HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP$([char]0x002f)IP Port\Ports\{0}" -f $this.PortName) # To take effect the spooler needs to be rebooted Restart-Service -Name "Spooler" -Force $newPrinterPortParams = @{ Name = $this.PortName } if ($null -ne $this.SNMPIndex) { $newPrinterPortParams.SNMP = $this.SNMPIndex $newPrinterPortParams.SNMPCommunity = $this.SNMPCommunity } switch ($this.PortType) { 'LPR' { $newPrinterPortParams.LprHostAddress = $this.Address $newPrinterPortParams.LprQueueName = $this.lprQueueName $newPrinterPortParams.LprByteCounting = $true } # End LPR 'TCPIP' { $newPrinterPortParams.PrinterHostAddress = $this.Address } # End TCPIP } # End Switch this.PortType Add-PrinterPort @newPrinterPortParams $updatePrinterParam = @{ Name = $this.Name PortName = $this.PortName } # Changing the printer to use the new port Set-Printer @updatePrinterParam # To clean up we will remove the temp printer port Remove-PrinterPort -Name $tempPort } # End Papercut 'LPR' { switch ($this.PortType) { 'TCPIP' { $UpdatePortParams = @{ Protocol = 1 PortNumber = 9100 } # Need to Use WMI as CIM has the objects as read only Get-WmiObject -Query ("Select * FROM Win32_TCPIpPrinterPort WHERE Name = '{0}'" -f $this.PortName ) | Set-WmiInstance -Arguments $UpdatePortParams -PutType UpdateOnly | Out-Null } # End TCPIP 'PaperCut' { $tempPort = $this.UseTempPort() Remove-PrinterPort -Name $this.PortName $this.CreatePaperCutPort() $updatePrinterParam = @{ Name = $this.Name PortName = $this.PortName } Set-Printer @updatePrinterParam # To clean up we will remove the temp printer port Remove-PrinterPort -Name $tempPort } # End PaperCut } # End Switch this.PortType } # End LPR 'TCPIP' { switch ($this.PortType) { 'LPR' { $UpdatePortParams = @{ Protocol = 2 Queue = $this.lprQueueName } # Need to Use WMI as CIM has the objects as read only Get-WmiObject -Query ("Select * FROM Win32_TCPIpPrinterPort WHERE Name = '{0}'" -f $this.PortName ) | Set-WmiInstance -Arguments $UpdatePortParams -PutType UpdateOnly | Out-Null } # End LPR 'PaperCut' { $tempPort = $this.UseTempPort() Remove-PrinterPort -Name $this.PortName $this.CreatePaperCutPort() $updatePrinterParam = @{ Name = $this.Name PortName = $this.PortName } Set-Printer @updatePrinterParam # To clean up we will remove the temp printer port Remove-PrinterPort -Name $tempPort } # End PaperCut } # End Switch this.PortType } # End TCPIP } # End Switch currentPortType # The ports were converted the setting will be in the desired state. return } # End If not CurrentPortType else { switch ($currentPortType) { 'PaperCut' { try { #To get Papercut address you need to look at the registry key $currentAddress = (Get-Item ("HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP/IP Port\Ports\{0}" -f $this.PortName) | Get-ItemProperty -ErrorAction Stop).HostName } catch { $currentAddress = '' } # End try/catch CurrentAddress if ($this.Address -ne $currentAddress) { Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f "Address", $this.Address, $currentAddress) $this.CreatePaperCutPort() #This will just update the registry keys } # End Address } # End PaperCut Default { $newPrinterPortParams = @{ Name = $this.PortName } # End newPrinterPortParams if ($currentPortType -eq 'LPR' -and $printerPort.lprQueueName -ne $this.lprQueueName) { Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f "lprQueueName", $this.lprQueueName, $printerPort.lprQueueName) $newPrinterPortParams.lprQueueName = $this.lprQueueName } # End If LprQueueName if ($this.Address -ne $printerPort.PrinterHostAddress) { Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f "Address", $this.Address, $printerPort.PrinterHostAddress) $newPrinterPortParams.PrinterHostAddress = $this.Address } # End If Address if ($this.SNMPIndex -ne 0 -and -not [string]::IsNullOrEmpty($this.SNMPCommunity)) { if ($this.SNMPCommunity -ne $printerPort.SNMPCommunity) { Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f "SNMPCommunity", $this.SNMPCommunity, $printerPort.SNMPCommunity) $newPrinterPortParams.SNMPCommunity = $this.SNMPCommunity } # End If SNMPCommunity if ($this.SNMPIndex -ne $printerPort.SNMPIndex) { Write-Verbose -Message ($this.Messages.UpdatedDesiredState -f "SNMPIndex", $this.SNMPIndex, $printerPort.SNMPIndex) $newPrinterPortParams.SNMPDevIndex = $this.SNMPIndex } # End If SNMPIndex } else { $newPrinterPortParams.SNMPEnabled = $false }# End If SNMP True # If newPrinterPortParams has more items than just Name the port needs to be updated with new settings if ($newPrinterPortParams.count -gt 1) { Get-WmiObject -Query ("Select * FROM Win32_TCPIpPrinterPort WHERE Name = '{0}'" -f $this.PortName ) | Set-WmiInstance -Arguments $newPrinterPortParams -PutType UpdateOnly | Out-Null } # End If newPrinterPortParams.Count } # End Default } # End Switch $currentPortType } # end if currentPortType } # End If not NewPrinterPort } else { if ($null -ne $printer) { $PrinterParams = @{ Name = $this.Name } Get-PrintJob -PrinterName $this.Name | Remove-PrintJob Remove-Printer @PrinterParams } # End If Printer if ($null -ne $printerPort) { try { Remove-PrinterPort -Name $this.PortName } catch { Restart-Service -Name Spooler -Force Remove-PrinterPort -Name $this.PortName } } # End If PrinterPort } # End Else absent } # End Set() [bool] Test() { try { $printer = Get-Printer -Name $this.Name -Full -ErrorAction Stop } catch { Write-Verbose -Message ($this.Messages.PrinterDoesNotExist -f $this.Name) $printer = $null } try { $printerPort = Get-PrinterPort -Name $this.PortName -ErrorAction Stop } catch { Write-Verbose -Message ($this.Messages.PrinterPortDoesNotExist -f $this.PortName) $printerPort = $null } if ($this.Ensure -eq [Ensure]::Present) { # region test current printer settings if ($null -eq $printer) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "Ensure", "Absent", $this.Ensure) return $false } # End Printer if ($null -eq $printerPort) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "PrinterPort", "Absent", $this.Ensure) return $false } # End PrinterPort if ($this.DriverName -ne $printer.DriverName) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "DriverName", $printer.DriverName, $this.DriverName) return $false } # End DriverName if ($null -ne $this.PermissionSDDL -and $this.PermissionSDDL -ne $printer.PermissionSDDL) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "PermissionSDDL", $printer.PermissionSDDL, $this.PermissionSDDL) return $false } # End PermissionSDDL if ($this.Shared -ne [System.Convert]::ToBoolean($printer.Shared)) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "Shared", $printer.Shared, $this.Shared) return $false } # End Shared if ($this.PortName -ne $printer.PortName) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "PortName", $printer.PortName, $this.PortName) return $false } # End PortName switch ($printerPort.Description) { "PaperCut TCP/IP Port" { try { #To get Papercut address you need to look at the registry key $currentAddress = (Get-Item ("HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP/IP Port\Ports\{0}" -f $this.PortName) | Get-ItemProperty).HostName } catch { $currentAddress = $null } if ($this.Address -ne $currentAddress) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "Address", $currentAddress, $this.Address) return $false } # End Address } # End PaperCut TCP/IP Port Default { if ($this.Address -ne $printerPort.PrinterHostAddress) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "Address", $printerPort.PrinterHostAddress, $this.Address) return $false } # End Address # Since SNMPIndex is always set, and the default is 0. We check to make sure if ($this.SNMPIndex -ne 0 -and -not [string]::IsNullOrEmpty($this.SNMPCommunity)) { if ($this.SNMPCommunity -ne $printerPort.SNMPCommunity) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "SNMPCommunity", $printerPort.SNMPCommunity, $this.SNMPCommunity) return $false } # End SNMPCommunity if ($this.SNMPIndex -ne $printerPort.SNMPIndex) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "SNMPIndex", $printerPort.SNMPIndex, $this.SNMPIndex) return $false } # End SNMPIndex } # End SNMPIndex if ($this.lprQueueName -ne $printerPort.lprQueueName) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "lprQueueName", $printerPort.lprQueueName, $this.lprQueueName) return $false } # End lprQueueName } # End Default } # End Switch # All the conditions have been met so we will return true so the set() method doesn't get called as everything is in a desired state. return $true } else { if ($null -ne $printer) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "Ensure", "Present", $this.Ensure) return $false } # End Printer if ($null -ne $printerPort) { Write-Verbose -Message ($this.Messages.NotInDesiredState -f "PrinterPort", "Present", $this.Ensure) return $false } # End PrinterPort return $true } # End Ensure } # End Test() [Printer] Get() { $ReturnObject = [Printer]::new() # Gathering the printer properties try { $printer = Get-Printer -Name $this.Name -Full -ErrorAction Stop } catch { $ReturnObject.Ensure = [Ensure]::Absent return $ReturnObject } try { $printerPort = Get-PrinterPort -Name $this.PortName -ErrorAction Stop } catch { $ReturnObject.Ensure = [Ensure]::Absent return $ReturnObject } # Both the printer and the printer port were found so we are going to set Ensure to Present $ReturnObject.Ensure = [Ensure]::Present if ($null -ne $printer) { $ReturnObject.Name = $printer.Name $ReturnObject.DriverName = $printer.DriverName $ReturnObject.Shared = $printer.Shared $ReturnObject.PermissionSDDL = $printer.PermissionSDDL } # End Printer if ($null -ne $printerPort) { $ReturnObject.PortName = $printerPort.Name switch ($printerPort.Description) { "PaperCut TCP/IP Port" { try { #To get Papercut address you need to look at the registry key $ReturnObject.Address = (Get-Item -Path ("HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP/IP Port\Ports\{0}" -f $this.PortName) | Get-ItemProperty).HostName } catch { $ReturnObject.Address = $null } #SNMP is disabled on papercut ports $ReturnObject.SNMPIndex = $null $ReturnObject.SNMPCommunity = $null $ReturnObject.PortType = [PortType]::PaperCut } # End PaperCut TCP/IP Port Default { $ReturnObject.Address = $printerPort.PrinterHostAddress $ReturnObject.SNMPIndex = $printerPort.SNMPIndex $ReturnObject.SNMPCommunity = $printerPort.SNMPCommunity $ReturnObject.PortType = [PortType]::TCPIP if ($printerPort.lprQueueName) { $ReturnObject.lprQueueName = $printerPort.lprQueueName $ReturnObject.PortType = [PortType]::LPR } } # End Default } # End Switch } # End PrinterPort return $ReturnObject } # End GET() hidden [void] CreatePaperCutPort() { # To create the PaperCut port we need to create a registry key however we can't use new-item due to 'PaperCut TCP/IP Port' the cmdlet switches / to a \. # Using the 'PaperCut TCP$([char]0x002f)IP Port' does not work. So we are just using reg.exe to add the key # Wrapping the Reg.exe commands in invoke-command to be able to create tests $null = Invoke-Command -ScriptBlock { param ( [Parameter()]$PortName, [Parameter()]$Address ) & "$env:windir\System32\reg.exe" ADD "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP/IP Port\Ports\$PortName" /v HostName /t REG_SZ /d $Address /f # Sets the port number to 9100 & "$env:windir\System32\reg.exe" ADD "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Monitors\PaperCut TCP/IP Port\Ports\$PortName" /v PortNumber /t REG_DWORD /d 0x0000238c /f } -ArgumentList ($this.PortName, $this.Address) # Need to restart the spooler service before the port is usable Restart-Service -Name 'Spooler' -Force } # End CreatePaperCutPort() hidden [System.String] FindPortType() { # Gathering the port information $getPortInformation = Get-CimInstance -Query ("Select Protocol,Description From Win32_TCPIpPrinterPort WHERE Name = '{0}'" -f $this.PortName) switch ($getPortInformation.Protocol) { 1 { # TCPIP return [PortType]::TCPIP } # End 1 2 { # LPR return [PortType]::LPR } # End 2 } # End Switch if ($getPortInformation.Description -eq "PaperCut TCP/IP Port") { return [PortType]::PaperCut } # End If Description return $null } # End FindPortType() hidden [System.String] UseTempPort() { # We required removing the existing port so a temp port needs to be created # We do a while loop to make sure the port name doesn't already exist $tempPortName = -join (1..9 | Get-Random -Count 5) while ($null -ne (Get-CimInstance -Query ("Select Name From Win32_TCPIpPrinterPort WHERE Name = '{0}'" -f $tempPortName)) ) { # We need to generate a new portName and then restart the $tempPortName = -join (1..9 | Get-Random -Count 5) } $tempPrinterPortParams = @{ Name = $tempPortName PrinterHostAddress = $this.Address } # End PrinterPortParams Add-PrinterPort @tempPrinterPortParams # We are updating the printer to use the new port while we convert the port to the desired type $tempPrinterParams = @{ Name = $this.Name PortName = $tempPortName } Set-Printer @tempPrinterParams return $tempPortName } # End UseTempPort() } # End Class |