U bent hier

Scan folders for the content of PDF files and send them to a printer using PowerShell

This is a solution I wrote when an existing tool was suspected of causing a memory leak in a Windows svchost process. This script scans a list of folders, and sends the found PDFs to a printer with the same name as the folder, unless mentioned otherwise in the .ini-file (see below).

  • This script runs hard-coded in a folder called c:\scripts and places feedback as .log files in a hard-coded folder called c:\scripts\logs (yes I know yadayada).
  • The include file GeneralPurposeLibrary.ps1 contains the function Get-IniContent which I copied from Oliver Lipkau (thanks). A copy of it is shown at the end of this page.
  • This script uses the command CLPrint, which I bought from TerminalWorks after evaluating some different tools doing this job.
  • It is a script to run in the task scheduler to be ignited at midnight and after a computer reboot/startup. That's why the script ends just before midnight. This results in the script being always active when the server is active. An improvement could be to have this run as a service.

Main script

. "C:\scripts\GeneralPurposeLibrary.ps1"
function Write-Logging {
    Param ([string]$pLogLine)
    $datetime = Get-Date -format "yyyyMMdd-HHmmss"
    $date = Get-Date -format "yyyyMMdd"
    $pLogLinePrefixed = $datetime + ": " + $pLogLine
    Add-content "C:\scripts\logs\MonitorFoldersAndPrintPDF-$date.log" -value $pLogLinePrefixed
}

function Get-NetworkPrinterName([string]$printer, [string]$format) {

    $aPrinterName=$null
    #Retrieve the UNC name of the defined printer
    $aPrinterName=$printer
    $RegistryPath = "Registry::HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Print\Providers\Client Side Rendering Print Provider\Servers"
    $RegistryKeys = Get-ChildItem -Path $RegistryPath
    foreach ($RegistryKey In $RegistryKeys) {
        $RegistryPrintersPath = "Registry::$RegistryKey\Printers"
        $RegistryPrintersKeys = Get-ChildItem -Path $RegistryPrintersPath
        foreach ($RegistryPrintersKey In $RegistryPrintersKeys) {
            $RegistryPrinterPath = "Registry::$RegistryPrintersKey\DsSpooler"
            #Here get the registry string value "printerName": if it is equal to '$printer'
            #  then get registry string value "serverName" or "shortServerName" to build the network name
            $RegistryPrinterPathElements = Get-ItemProperty -Path $RegistryPrinterPath -ErrorAction SilentlyContinue
            if (!($RegistryPrinterPathElements -eq $null)) {
                if ($RegistryPrinterPathElements.printerName -eq $printer) {
                    if ($format -eq "long") {
                        $aPrinterName = "\\" + $RegistryPrinterPathElements.serverName +  "\" + $RegistryPrinterPathElements.printerName
                    }
                    if ($format -eq "short") {
                        $aPrinterName = "\\" + $RegistryPrinterPathElements.shortServerName +  "\" + $RegistryPrinterPathElements.printerName
                    }
                }
            }
                            
        }
    }
    if ($aPrinterName -ne $null) {
        Write-Logging ("      --> Registry $format name for $printer is $aPrinterName")
    } else {
        Write-Logging ("      --> Registry $format name for $printer not found in registry")
    }
    return $aPrinterName
}

function Print-ToPrinterDeviceI([string]$fileName, [string]$printer, [boolean]$reportErrorViaSMTP) {
    $aPrintResult = $true

    #Build command and arguments
    $command = "c:\scripts\clprint"
    $arguments = @("/print", "/pdffile", $fileName, "/printer:$printer")

    Write-Logging ("      ==> $command $arguments")
    
    #Run print command
    $resultString=""
    & "$command" $arguments | Tee-Object -Variable resultString

    if ($resultString.StartsWith("Error")) {
        $aPrintResult=$false
        Write-Logging ("          failed ("+$resultString+")")
        if ($reportErrorViaSMTP -eq $true) {
            #report error (via mail) if required & move to error-folder if required
            Write-Logging ("             (mailed to $errorMailAddres)")
            $smtpServer = "smtp.example.com"
            $smtpFrom = "noreply@example.com"
            $smtpTo = $errorMailAddress
            $messageSubject = "Could not print invoice " + $fileName + " on " + $printer 
            $body = "CLPrint in c:\scripts on someserver.domain.example.com returned:`r`n"+$resultString+"`r`n `r`n `r`n"+$fileName+" (moved to error folder)`r`n Base location: "+$printerFolder.FullName+"(possibly overwritten in .ini file)"
            send-mailmessage -from $smtpFrom -to $smtpTo -subject "$messageSubject" -body "$body" -smtpServer $smtpServer
        }
    }

    return $aPrintResult
}

function Print-ToPrinterDevice([string]$fileName, [array]$printers) {
    $aPrintResult = Print-ToPrinterDeviceI $fileName $printers.short $false
    if ($aPrintResult -eq $false) {
        $aPrintResult = Print-ToPrinterDeviceI $fileName $printers.long $true
    }
    return $aPrintResult
}

#Configuration
$config = Get-IniContent "MonitorFoldersAndPrintPDF.ini"
$rootfolder = $config["General"]["RootFolder"]
$errorMailAddress = $config["General"]["ErrorMailAddress"]
Write-Logging ("Imported ini-file, root-folder is $rootfolder")

$prvDayRegistration = Get-Date -format "dd"
$activeLoop=$true
While  ($activeLoop) {
    #Run for each printer (which is a folder in $rootfolder)
    $printers = get-childitem -path $rootfolder | where { $_.PSIsContainer }
    foreach ($printerFolder In $printers) {
        #Retrieve list of files in P:\«printer-name»
        $files = get-childitem -path $printerFolder.FullName | where { -not $_.PSIsContainer }
        
        #If there is at least one file found in the printer folder
        if ($files) {
            #redirect if it is defined in the .ini-file
            if ($config["PrinterRedirects"][$printerFolder.Name] -eq $null) {
                $printerDevice = $printerFolder.Name
            } else {
                $printerDevice = $config["PrinterRedirects"][$printerFolder.Name];
                Write-Logging ("   Redirecting $printerFolder to $printerDevice")
            }

            #Get long and short network name for the printer
            $RegistryPrinter = @{ "long" = Get-NetworkPrinterName $printerDevice "long"; "short" = Get-NetworkPrinterName $printerDevice "short"}

            #Process every file found in the printer folder
            foreach ($file In $files) {
                if ($file.Extension -eq ".pdf") {

                    #Actual print-process of one document
                    $printResult = Print-ToPrinterDevice $file.FullName $RegistryPrinter

                    #Move to either subfolder 'error' or 'printed'
                    if ($printResult -eq $true) {
                        $newFolder=$printerFolder.FullName+"\printed\"
                    } else {
                        $newFolder=$printerFolder.FullName+"\error\"
                    }
                       if(!(Test-Path -Path $newFolder )){
                        New-Item -ItemType directory -Path $newFolder
                    }
                       $newName = $newFolder+$file.name
                    Move-Item -path $file.Fullname -Destination $newName -force
                }
            }
        } else {
            #Write-Logging ("   Folder "+$printerFolder.FullName+": no files to process")
        }
    }
    Start-Sleep -s 5
    $Day = Get-Date -format "dd"
    $HourMinutes = Get-Date -format "HHmm"
    if ([int]$HourMinutes -ge 2357) {
        $activeLoop=$false;
    } else {
        # in case a loop took longer than 3 minutes and skipped through midnight...
        if ($Day -ne $prvDayRegistration) {
            $activeLoop=$false;
        }
    }
}
Write-Logging ("Normal end")

Configuration settings

The folder where the printer-folders reside that need to be scanned, an email address to report errors to and possible printer-redirections are placed in a .ini file

[General]
RootFolder=D:\some-folder
errorMailAddress=mailboxtoinform@example.com

[PrinterRedirects]
PRT001=PRT003

In the above example, PRT001 might be a printer which is (temporarilly?) offline and which prints need to be sent to PRT003. This PrintRedirects-settings can also be used in case you have other folder names than printer names and the folder names, nor the printer names can be renamed...

Get-IniContent

So again: this script I copied from Oliver Lipkau (http://oliver.lipkau.net/blog). It is a good working script, nicely written:

Function Get-IniContent 

    <# 
    .Synopsis 
        Gets the content of an INI file 
         
    .Description 
        Gets the content of an INI file and returns it as a hashtable 
         
    .Notes 
        Author    : Oliver Lipkau <oliver@lipkau.net
        Blog      : http://oliver.lipkau.net/blog/ 
        Date      : 2014/06/23 
        Version   : 1.1 
         
        #Requires -Version 2.0 
         
    .Inputs 
        System.String 
         
    .Outputs 
        System.Collections.Hashtable 
         
    .Parameter FilePath 
        Specifies the path to the input file. 
         
    .Example 
        $FileContent = Get-IniContent "C:\myinifile.ini" 
        ----------- 
        Description 
        Saves the content of the c:\myinifile.ini in a hashtable called $FileContent 
     
    .Example 
        $inifilepath | $FileContent = Get-IniContent 
        ----------- 
        Description 
        Gets the content of the ini file passed through the pipe into a hashtable called $FileContent 
     
    .Example 
        C:\PS>$FileContent = Get-IniContent "c:\settings.ini" 
        C:\PS>$FileContent["Section"]["Key"] 
        ----------- 
        Description 
        Returns the key "Key" of the section "Section" from the C:\settings.ini file 
         
    .Link 
        Out-IniFile 
    #> 
     
    [CmdletBinding()] 
    Param( 
        [ValidateNotNullOrEmpty()] 
        [ValidateScript({(Test-Path $_) -and ((Get-Item $_).Extension -eq ".ini")})] 
        [Parameter(ValueFromPipeline=$True,Mandatory=$True)] 
        [string]$FilePath 
    ) 
     
    Begin 
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function started"} 
         
    Process 
    { 
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Processing file: $Filepath" 
             
        $ini = @{} 
        switch -regex -file $FilePath 
        { 
            "^\[(.+)\]$" # Section 
            { 
                $section = $matches[1] 
                $ini[$section] = @{} 
                $CommentCount = 0 
            } 
            "^(;.*)$" # Comment 
            { 
                if (!($section)) 
                { 
                    $section = "No-Section" 
                    $ini[$section] = @{} 
                } 
                $value = $matches[1] 
                $CommentCount = $CommentCount + 1 
                $name = "Comment" + $CommentCount 
                $ini[$section][$name] = $value 
            }  
            "(.+?)\s*=\s*(.*)" # Key 
            { 
                if (!($section)) 
                { 
                    $section = "No-Section" 
                    $ini[$section] = @{} 
                } 
                $name,$value = $matches[1..2] 
                $ini[$section][$name] = $value 
            } 
        } 
        Write-Verbose "$($MyInvocation.MyCommand.Name):: Finished Processing file: $path" 
        Return $ini 
    } 
         
    End 
        {Write-Verbose "$($MyInvocation.MyCommand.Name):: Function ended"} 
}