PrintHA.DSC.psm1

enum Ensure
{
    Absent
    Present
}

enum FileMirrorState
{
    Same
    SourceNewer
    DestinationNewer
    SourceMissing
    DestinationMissing
    SourceMissingDestinationMissing
}

enum PrinterAction
{
    None
    Create
    Remove
}

<#
    This resource manages the presence of the XML configuration file (specific for each computer) copied from a specified source folder.
#>

[DscResource()]
class cFileMirror
{
    # The source folder containing XML configuration files
    [DscProperty(Mandatory)]
    [String]$SourceFolder

    # The fully qualified name of the target file
    [DscProperty(Key)]
    [String] $DestinationPath    
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure    

    [String]$SourcePath 

    [void]Set()
    {
        Write-verbose '[Set] Entering'
        
        # Compose the source file name in runtime
        $this.SourcePath = $this.SourceFolder + '\' + $env:COMPUTERNAME + '.xml'

        # Get the current config file status
        [FileMirrorState]$State = $this.GetConfigStatus()
        if($this.Ensure -eq [Ensure]::Present)
        {
            switch($State)
            {
                {([FileMirrorState]::Same, [FileMirrorState]::SourceMissingDestinationMissing) -contains $_} {break}
                'SourceMissing' {
                    Write-verbose '[Set] Removing Destination'
                    Remove-Item "$($this.DestinationPath)" -Force -ErrorAction 'SilentlyContinue'
                    break
                }
                default {
                    Write-verbose '[Set] Updating Destination'
                    Copy-Item "$($this.SourcePath)" "$($this.DestinationPath)" -Force
                }
            }
        }
        else
        {
            switch($State)
            {
                {([FileMirrorState]::DestinationMissing, [FileMirrorState]::SourceMissingDestinationMissing) -contains $_} {break}
                default {
                    Write-Verbose '[Set] Removing Destination'
                    Remove-Item "$($this.DestinationPath)" -Force -ErrorAction 'SilentlyContinue'
                }

            }

        }
        Write-Verbose '[Set] Returning'
    }
    
    [cFileMirror]Get()
    {
        Write-verbose '[Get] Entering'

        Write-verbose '[Get] Returning'
        return $this
    }
    
    [bool]Test()
    {
        Write-verbose '[Test] Entering'
        
        # Compose the source file name in runtime
        $this.SourcePath = $this.SourceFolder + '\' + $env:COMPUTERNAME + '.xml'
        [bool]$retVal = $false

        # Get the current config file status
        [FileMirrorState]$State=$this.GetConfigStatus()
        if($this.Ensure -eq [Ensure]::Present)
        {
            switch($State)
            {
                {([FileMirrorState]::Same, [FileMirrorState]::SourceMissingDestinationMissing) -contains $_} { $retVal = $true; break}
            }
        }
        else
        {
            switch($State)
            {
                {([FileMirrorState]::DestinationMissing, [FileMirrorState]::SourceMissingDestinationMissing) -contains $_}  { $retVal = $true; break}
            }
        }
        
        Write-verbose "[Test] Returning: $retVal"
        return $retVal
    }

    <#
        Returns the current status of the configuration file as the result of comparison of time stamps of the source and the destination file.
    #>

    [FileMirrorState]GetConfigStatus()
    {
         Write-Verbose "[GetConfigStatus] Source: $($this.SourcePath)"
         if(Test-Path "$($this.SourcePath)")
         {
            #file exists in source
            Write-Verbose '[GetConfigStatus] Source exists'
            $srcFile=Get-Item "$($this.SourcePath)"
            if(Test-Path "$($this.DestinationPath)")
            {
                #destination file exists - overwrite if newer
                Write-verbose '[GetConfigStatus] Destination exists'
                $dstFile=Get-Item "$($this.DestinationPath)"
                if($srcFile.LastWriteTime -gt $dstFile.LastWriteTime)
                {
                    Write-verbose '[GetConfigStatus] Source newer'
                    return [FileMirrorState]::SourceNewer
                }
                if($srcFile.LastWriteTime -eq $dstFile.LastWriteTime)
                {
                    Write-verbose '[GetConfigStatus] Destination same'
                    return [FileMirrorState]::Same
                }
                Write-verbose '[GetConfigStatus] Destination newer'
                return [FileMirrorState]::DestinationNewer
            }
            else
            {
                #destination file does not exist - copy from source
                Write-Verbose '[GetConfigStatus] Destination does not exist'
                return [FileMirrorState]::DestinationMissing
            }
        }
        else
        {
            Write-verbose '[GetConfigStatus] Source does not exist'
            if(Test-Path "$($this.DestinationPath)")
            {
                Write-verbose '[GetConfigStatus] Destination exists'
                return [FileMirrorState]::SourceMissing
            }
            Write-verbose '[GetConfigStatus] Destination does not exist'
            return [FileMirrorState]::SourceMissingDestinationMissing
        }
    } 
}

<#
    This resource manages print drivers installed on the local computer according to the corresponding configuration DB (XML file).
#>

[DscResource()]
class cPrintDriverUpdater
{
    # The root folder where the drivers database (XML file) is stored
    [DscProperty(Key)]
    [String]$DriversRoot
    
    # An optional suffix representing the version of the drivers DB
    [DscProperty()]
    [String]$DBVersion
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure    

    # The registry key where the status is stored
    [DscProperty()]
    [string]$RegConfigRoot="HKLM:\SOFTWARE\GreyCorbel\DSC\PrintHA"

    # The fully qualified path to an optional registry file
    [DscProperty()]
    [String]$RegFilePath

    # The switch controlling whether the $RegFilePath file is to be imported before installing the driver.
    [DscProperty()]
    [bool]$ApplyRegChangesBeforeDriverUpdate = $false

    # The Name of the global driver list
    [string]$DBNameRoot="DriverDatabase"    
    
    [string]$DBName
    
    [void]Set()
    {
        Write-verbose '[Set] Entering'
        if(-not (Test-Path "$($this.RegConfigRoot)"))
        {
            New-Item -Path "$($this.RegConfigRoot)" -ItemType Directory -Force | Out-Null
        }
        
        if(-not [string]::IsNullOrEmpty($this.DBVersion))
        {
            $this.DBName = $this.DBNameRoot + '_' + $this.DBVersion + ".xml"
        }
        else 
        {
            $this.DBName=$this.DBNameRoot + ".xml";
        }

        # Apply registry changes before driver update
        if ((-not [System.String]::IsNullOrEmpty($this.RegFilePath)) -and ($this.ApplyRegChangesBeforeDriverUpdate))
        {
            Write-Verbose ('[Set] Applying registry file: ' + $this.RegFilePath)
            Update-Registry -Ensure $this.Ensure -RegFilePath $this.RegFilePath
        }
        
        if($this.Ensure -eq [Ensure]::Present)
        {
            if(Test-Path "$($this.DriversRoot)\$env:COMPUTERNAME`.xml")
            {
                (Update-PnPDrivers -DriversRoot $this.DriversRoot -DriversDB "$env:COMPUTERNAME`.xml") | Write-Verbose
                Set-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Machine -Value ([System.IO.File]::GetLastWriteTimeUtc("$($this.DriversRoot)\$env:COMPUTERNAME`.xml")).ToFileTimeUtc()
            }
            else
            {
                (Update-PnPDrivers -DriversRoot $this.DriversRoot -DriversDB $this.DBName) | Write-Verbose
                Set-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Global -Value ([System.IO.File]::GetLastWriteTimeUtc("$($this.DriversRoot)\$($this.DBName)")).ToFileTimeUtc()
            } 
        }
        else
        {
            Write-verbose '[Set] Removing all drivers'
            Get-PnPDriver | Remove-PnPDriver | Out-Null
            # Reset cached data
            Set-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Global -Value 0
            Set-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Machine -Value 0
        }

        # Apply registry changes after driver update
        if ((-not [System.String]::IsNullOrEmpty($this.RegFilePath)) -and (-not $this.ApplyRegChangesBeforeDriverUpdate))
        {
            Write-Verbose ('[Set] Applying registry file: ' + $this.RegFilePath)
            Update-Registry -Ensure $this.Ensure -RegFilePath $this.RegFilePath
        }

        Write-Verbose '[Set] Returning'
    }
    
    [cPrintDriverUpdater]Get()
    {
        Write-Verbose '[Get] Entering'

        Write-Verbose '[Get] Returning'
        return $this
    }
    
    [bool]Test()
    {
        Write-Verbose '[Test] Entering'
        [bool]$retVal=$false
        if($this.Ensure -eq [Ensure]::Present)
        {
            $retVal = -not ($this.GetConfigExpirationStatus())
        }

        # Test registry
        if ($retVal)
        {
            $updater = New-Object -TypeName cRegistryUpdater
            $updater.Ensure = $this.Ensure

            if (-not [System.String]::IsNullOrEmpty($this.RegFilePath))
            {
                $updater.RegFilePath = $this.RegFilePath
                $retVal = $updater.Test()
            }
            
            if ($retVal)
            {
                $dDatabaseName = Get-DriverDatabaseName -DBNameRoot $this.DBNameRoot -DriversRoot $this.DriversRoot -DBVersion $this.DBVersion

                [XML]$xml = Get-Content -Path $dDatabaseName -Force
                foreach ($driver in $xml.PrintDrivers.driver)
                {
                    if ($driver.regFilePath)
                    {
                        $updater.RegFilePath = $driver.regFilePath
                        $retVal = $updater.Test()

                        if (-not $retVal) { break }
                    }
                }
            }
        }

        #Absent always returns false
        Write-Verbose "[Test] Returning: $retVal"
        return $retVal
    }
    
    [bool]GetConfigExpirationStatus()
    {
        [bool]$retVal=$false
        [datetime]$LastUpdatedCached=[datetime]::MinValue
        [datetime]$LastUpdatedReal=[datetime]::MaxValue
        
        Write-Verbose '[GetConfigExpirationStatus] Entering'
        
        if(-not [string]::IsNullOrEmpty($this.DBVersion))
        {
            $this.DBName = $this.DBNameRoot + '_' + $this.DBVersion + ".xml"
        }
        else 
        {
            $this.DBName=$this.DBNameRoot + ".xml"
        }
        
        if(Test-Path "$($this.DriversRoot)\$env:COMPUTERNAME`.xml")
        {
            Write-Verbose '[GetConfigExpirationStatus] Machine config exists'
            $data=Get-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Machine -ErrorAction SilentlyContinue
            if($data -ne $null)
            {
                $LastUpdatedCached=[DateTime]::FromFileTimeUtc($data.LastUpdated_Machine)
            }
            $LastUpdatedReal=[System.IO.File]::GetLastWriteTimeUtc("$($this.DriversRoot)\$env:COMPUTERNAME`.xml")
        }
        else {
            Write-Verbose "[GetConfigExpirationStatus] Machine config does not exist, using global: $($this.DriversRoot)\$($this.DBName)"
            $data=Get-ItemProperty -Path "$($this.RegConfigRoot)" -Name LastUpdated_Global -ErrorAction SilentlyContinue
            if($data -ne $null)
            {
                $LastUpdatedCached=[DateTime]::FromFileTimeUtc($data.LastUpdated_Global)
            }
            $LastUpdatedReal=[System.IO.File]::GetLastWriteTimeUtc("$($this.DriversRoot)\$($this.DBName)")

        }
        $retVal = ($LastUpdatedReal -gt $LastUpdatedCached)
        Write-Verbose "[GetConfigExpirationStatus] Cached LastUpdate: $LastUpdatedCached"
        Write-Verbose "[GetConfigExpirationStatus] DB LastUpdate: $LastUpdatedReal"
        Write-Verbose "[GetConfigExpirationStatus] Returning: $retVal"
        return $retVal    
    }    
}

# Gets full name of driver database (XML)
function Get-DriverDatabaseName
{
    param(
        [Parameter(Mandatory=$true)][String]$DBNameRoot,
        [Parameter(Mandatory=$true)][String]$DriversRoot,
        [Parameter(Mandatory=$false)][String]$DBVersion
    )

    if(-not [string]::IsNullOrEmpty($DBVersion))
    {
        $dbName = $DBNameRoot + '_' + $DBVersion + ".xml"
    }
    else 
    {
        $dbName = $DBNameRoot + ".xml"
    }

    if(Test-Path "$DriversRoot\$env:COMPUTERNAME.xml") 
    {
        return "$DriversRoot\$env:COMPUTERNAME.xml"
    }
    else
    {
        return "$DriversRoot\$dbName"
    }
}

# Updates registry through cRegistryUpdater resource class
function Update-Registry
{
    param(
        [Parameter(Mandatory)][Ensure]$Ensure,
        [Parameter(Mandatory)][String]$RegFilePath
    )

    process
    {
        Write-Verbose '[Update-Registry] Entering'
        $updater = New-Object -TypeName cRegistryUpdater
        $updater.Ensure = $Ensure
        $updater.RegFilePath = $RegFilePath
        $updater.Set()
        Write-Verbose '[Update-Registry] Returning'
    }
}

##### PnP API ########


Function Install-PnPDriver([String[]]$InfPath)
{
        Format-PnPUtilOutput(pnputil -a "$InfPath")
}

Function Get-PnPDriver
{
    Format-PnPUtilOutput(pnputil -e)
}

Function Remove-PnPDriver
{
    Param(
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        [Object[]]$PnPDriver
    )
    Process
    {
        if($PnPDriver -eq $null) {return}
        foreach($DriverDef in $PnpDriver)
        {
            if($DriverDef."Published Name" -ne $null)
            {
                $PublishedName=$DriverDef."Published Name"
            }
            else
            {
                $PublishedName=$DriverDef
            }
            Format-PnPUtilOutput(pnputil -d $PublishedName)
        }
    }
}


##### Internals ########

Function Update-PnPDrivers
{
    Param
    (
        [String]$DriversRoot,
        [String]$DriversDB
    )

    Process
    {
        $data=[xml](Get-Content "$DriversRoot\$DriversDB")
        foreach($driver in $data.PrintDrivers.driver)
        {
            Install-PnPDriver("$DriversRoot\$($driver.InfPath)")

            #Apply registry changes
            if ($driver.regFilePath) 
            {
                Write-Verbose ('[Update-PnPDrivers] Applying registry file: ' + $driver.regFilePath)
                Update-Registry -Ensure ([Ensure]::Present) -RegFilePath $driver.regFilePath
            }
        }
    }
}

Function Format-PnPUtilOutput([String[]]$rawData)
{
    $currentObj=new-object PSCustomObject
    foreach($line in $rawData)
    {
        $idx=$line.IndexOf(" : ")
        if($idx -eq -1)
        {
            continue
        }
        $key=$line.Substring(0,$idx)
        $val=$line.Substring($idx+3, $line.Length-$idx-3).Trim()
        if($currentObj.$key -ne $null)
        {
            $currentObj
            $currentObj=new-object PSCustomObject
        }
        Add-Member -InputObject $currentObj -MemberType NoteProperty -Name $key -Value $val
    }
    $currentObj
}

##### Print WMI ########

Function Get-PrinterWMI
{
    Param
    (
        [Parameter(Mandatory=$false)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        Write-verbose "[Get] Entering Get-PrinterWMI Function"
        if ($Name)
        {
            $result = Get-WmiObject -Namespace Root\cimv2 -class Win32_Printer -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        }
        Return $Result
    }
}

Function Get-PrintConfigurationWMI
{
    Param
    (
        [Parameter(Mandatory=$false)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        Write-verbose "[Get] Entering Get-PrintConfigurationWMI Function"
        if ($Name)
        {
            $result = Get-WmiObject -Namespace Root\cimv2 -class Win32_PrinterConfiguration -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        }
        Return $Result
    }
}


Function Get-PrinterPortWMI
{
    Param
    (
        [Parameter(Mandatory=$false)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $result = Get-WmiObject -Namespace Root\cimv2 -class Win32_TcpIpPrinterPort -ComputerName $ComputerName | Where-Object -Property name -imatch -Value "$Name"
        Return $Result
    }
}

Function Get-PrinterDriverWMI
{
    Param
    (
        [Parameter(Mandatory=$false)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_PrinterDriver -ComputerName $ComputerName | Where-Object -Property name -imatch -Value "$Name"
        Return $Result
    }
}

Function add-PrinterPortWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$true)]
        [String]$PrinterHostAddress,
        [Parameter(Mandatory=$false)]
        [String]$PortNumber = "9100",
        [Parameter(Mandatory=$false)]
        [INT]$Protocol = 1,
        [Parameter(Mandatory=$False)]
        $SnmpEnabled = $False,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $port = [wmiclass]"\\$ComputerName\Root\cimv2:Win32_TcpIpPrinterPort" 
        $port.psbase.scope.options.EnablePrivileges = $true 
        $newPort = $port.CreateInstance() 
        $newport.name = $Name 
        $newport.Protocol = $Protocol 
        if ($PrinterHostAddress -ne $null)
        {
            $newport.HostAddress = $PrinterHostAddress 
        }
        $newport.PortNumber = $PortNumber
        $newport.SnmpEnabled = $SnmpEnabled 
        $newport.Put()
        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_TcpIpPrinterPort -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        Return $Result
    }
}


Function add-PrinterDriverWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $printdriver = [wmiclass]"\\$ComputerName\Root\cimv2:Win32_PrinterDriver"
        $driver = $printdriver.CreateInstance()
        $driver.Name= $Name
        $printdriver.AddPrinterDriver($driver)
        $printdriver.Put()
        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_PrinterDriver -ComputerName $ComputerName | Where-Object -Property name -like -Value "$Name*" | Where-Object -Property SupportedPlatform -EQ -Value $platform
        Return $Result
    }
}

Function add-PrinterWMI
{
    Param
    (
        [Parameter(Mandatory=$True)]
        [String]$DriverName,
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$true)]
        [String]$PortName,
        [Parameter(Mandatory=$false)]
        [String]$Location,
        [Parameter(Mandatory=$false)]
        [String]$Comment,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $wmi = ([WMIClass]"\\$ComputerName\Root\cimv2:Win32_Printer")
        $Printer = $wmi.CreateInstance()
        $Printer.DriverName = $DriverName
        $Printer.PortName = $PortName
        if ($Location -ne $null)
        {
            $Printer.Location = $Location
        }
        if ($Comment -ne $null)
        {
            $Printer.Comment = $Comment
        }
        $Printer.Name = $Name
        #$Printer.Caption = $Name
        $Printer.DeviceID = $Name
        $Printer.Put()
        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_Printer -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        Return $Result

    }
}

Function Remove-PrinterWMI
{
    Param
    (
        [Parameter(Mandatory=$false)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $result = Get-WmiObject -Namespace Root\cimv2 -class Win32_Printer -ComputerName $ComputerName | Where-Object -Property name -CMatch -Value $Name
        Try 
        {
            $result | Remove-WmiObject
        } Catch
        {
            Return $Error[0]
        }
        Return $Result
    }
}

Function Remove-PrinterPortWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $result = Get-WmiObject -Namespace Root\cimv2 -class Win32_TcpIpPrinterPort -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        $result | Remove-WmiObject
        Return $Result
    }
}

Function Remove-PrinterDriverWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        if ($Name -in ((Get-PrinterWMI).DriverName))
        {
            $UsedBy = get-printerwmi | Where-Object -Name DriverName -eq -Value $name
            $Message = "Cannot delete. Driver is used by printer " + $UsedBy.name 
            Return $Message
        }
        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_PrinterDriver -ComputerName $ComputerName | Where-Object -Property name -like -Value "$Name,*"
        Return $Result
    }
}

Function Set-PrinterWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [String]$Shared,
        [Parameter(Mandatory=$false)]
        [String]$ShareName,
        [Parameter(Mandatory=$false)]
        [String]$Published,
        [Parameter(Mandatory=$false)]
        [String]$Comment,
        [Parameter(Mandatory=$false)]
        [string]$Location,
        [Parameter(Mandatory=$false)]
        [String]$ComputerName = $env:COMPUTERNAME
    )

    Process
    {
        $Printer = Get-WmiObject -Namespace Root\cimv2 -class Win32_Printer -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        # Share/Unshare Printer
        Switch ($Shared)
        {
            $True
            {
                if (!$ShareName)
                {
                    $ShareName = $Printer.name
                }
                $Printer.shared = $true
                $Printer.ShareName = $ShareName
            }
            $False
            {
                $Printer.shared = $false
            }
            Default{}
        }
        $Printer.put()

        # Publised/Not Published in Active Directory
        Switch ($Published)
        {
            $True
            {
                $Printer.Published=$true
            }
            $False
            {
                $Printer.Published=$false
            }
            Default{}
        }
        $Printer.put()

        # Comment Printer
        if ($Comment)
        {
            $Printer.Comment = $Comment
        }

        $Printer.put()

        #Printer Location
        if ($Location)
        {
            $Printer.Location = $Location
        }
        $Printer.put()

        $Result = Get-WmiObject -Namespace Root\cimv2 -class Win32_Printer -ComputerName $ComputerName | Where-Object -Property name -EQ -Value $Name
        Return $Result
    }
}


Function Set-PrintConfigurationWMI
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [String]$Name,
        [Parameter(Mandatory=$false)]
        [bool]$Color,
        [Parameter(Mandatory=$false)]
        [bool]$Collation,
        [Parameter(Mandatory=$false)]
        [String]$PaperSize,
        [Parameter(Mandatory=$false)]
        [String]$DuplexingMode,
        [Parameter(Mandatory=$false)]
        [String]$PageOrientation
    )

    Process
    {
        Write-verbose "[Set] Entering Set-PrintConfigurationWMI Function"
        $Dirty = 0
        Add-Type -AssemblyName System.Printing
        $permAdminServer = ([System.Printing.PrintSystemDesiredAccess] "AdministrateServer")
        $permAdminPrinter = ([System.Printing.PrintSystemDesiredAccess] "AdministratePrinter")
        $ps = New-Object System.Printing.PrintServer $permAdminServer
        $queue = $ps.GetPrintQueues() | Where-Object -Property name -EQ -Value $Name
        $QueuePrintCapabilities = $queue.GetPrintCapabilities()
        $queue2 = New-Object System.Printing.PrintQueue $ps,$queue.Name,$permAdminPrinter
        if ($Color -ne $null)
        {
            Switch ($Color)
            {
                $True
                {
                    $queue2.DefaultPrintTicket.OutputColor = "Color"
                    $queue2.UserPrintTicket.OutputColor = "Color"
                    $Dirty ++
                }
                $False
                {
                    $queue2.DefaultPrintTicket.OutputColor = "Monochrome"
                    $queue2.UserPrintTicket.OutputColor = "Monochrome"
                    $Dirty ++
                }
                Default
                {
                Write-Host "Color parameter value unsupported!"
                }
            }
        }
        if ($Dirty -gt 0)
        {
            $queue2.Commit()
            $Dirty = 0
        }
        if ($Collation -ne $null)
        {
            Switch ($Collation)
            {
                $True
                {
                    $queue2.DefaultPrintTicket.Collation = "Collated"
                    $queue2.UserPrintTicket.Collation = "Collated"
                    $Dirty ++
                }
                $False
                {
                    $queue2.DefaultPrintTicket.Collation = "UnCollated"
                    $queue2.UserPrintTicket.Collation = "UnCollated"
                    $Dirty ++
                }
                Default
                {
                Write-Host "Collation parameter value unsupported!"
                }
            }
        }
        if ($Dirty -gt 0)
        {
            $queue2.Commit()
            $Dirty = 0
        }

        if ($PaperSize)
        {
            if ($PaperSize -notin ($QueuePrintCapabilities.PageMediaSizeCapability.PageMediaSizeName))
            {
                Write-Host "PaperSize parameter value unsupported!" -ForegroundColor Yellow
                $QueuePrintCapabilities.PageMediaSizeCapability | Sort-Object | write-host -ForegroundColor Yellow
                Break
            }
            $property = New-Object System.Printing.PageMediaSize([System.Printing.PageMediaSizeName]::$PaperSize)
            $queue2.DefaultPrintTicket.PageMediaSize = $property
            $queue2.UserPrintTicket.PageMediaSize = $property
            $Dirty ++
            if ($Dirty -gt 0)
            {
                $queue2.Commit()
                $Dirty = 0
            }
        }

        if ($DuplexingMode)
        {
            if ($DuplexingMode -notin ($QueuePrintCapabilities.DuplexingCapability))
            {
                Write-Host "DuplexingMode parameter value unsupported!" -ForegroundColor Yellow
                $QueuePrintCapabilities.DuplexingCapability | write-host -ForegroundColor Yellow
            }
            $queue2.DefaultPrintTicket.Duplexing = $DuplexingMode
            $queue2.UserPrintTicket.Duplexing = $DuplexingMode
            $Dirty ++
            if ($Dirty -gt 0)
            {
                $queue2.Commit()
                $Dirty = 0
            }
        }

        if ($PageOrientation)
        {
            if ($PageOrientation -notin ($QueuePrintCapabilities.PageOrientationCapability))
            {
                Write-Host "PageOrientation parameter value unsupported!" -ForegroundColor Yellow
                $QueuePrintCapabilities.PageOrientationCapability | write-host -ForegroundColor Yellow
            }
            $queue2.DefaultPrintTicket.PageOrientation = $PageOrientation
            $queue2.UserPrintTicket.PageOrientation = $PageOrientation
            $Dirty ++
            if ($Dirty -gt 0)
            {
                $queue2.Commit()
                $Dirty = 0
            }
        }
    }
}

# Checks whether PrintManagement PS module is available (Windows Server 2012 or newer)
function Get-PrintManagementAvailable
{
    $pm = Get-Module -Name PrintManagement
    if ($pm -eq $null) {
        $pm = Get-Module -ListAvailable|Where-Object -Property Name -EQ -Value PrintManagement
    }

    return ($pm -ne $null)
}

function Get-PrinterDACLWMI
{
    param(
        [Parameter(Mandatory=$true)]$printerName
    )
    Write-Verbose '[Get-PrinterDACLWMI]Entering'

    $printer = Get-WmiObject -Class Win32_Printer -Filter ('name = ''' + $printerName + '''') -ErrorAction SilentlyContinue
    if ($printer -eq $null) {
        Write-Verbose ('[Set-PrinterDACLWMI]Printer not found: ' + $printerName)
        return ''
    }

    $sd = $printer.GetSecurityDescriptor().Descriptor
    $result = ([WMIClass]'Win32_SecurityDescriptorHelper').Win32SDToSDDL($sd)

    if ($result.ReturnValue -ne 0) {
        Write-Verbose ('[Set-PrinterDACLWMI]Translation of security descriptor to SDDL failed with this exit code: ' + $result.ReturnValue)
        return ''
    }

    return $result.SDDL
}

# Parses DACL from JSON string and updates security descriptor of the printer
function Set-PrinterDACL
{
    param(
        [Parameter(Mandatory=$true)][String]$printerName,
        [Parameter(Mandatory=$true)][String]$jsonDACL,
        [Parameter(Mandatory=$false)][bool]$noDefaultAdminPermissions = $false
    )

    Write-Verbose '[SetPrinterDACL]Entering'

    # Use PrintManagement PS module when available
    if (Get-PrintManagementAvailable)
    {
        $p = Get-Printer -Name $printerName -Full -ErrorAction SilentlyContinue
        if ($p -eq $null) 
        { 
            Write-Verbose ('[SetPrinterDACL]Unable to get printer object: ' + $printerName)
            return $false
        }

        $origSDDL = $p.PermissionSDDL
        $origDACL = [System.Text.RegularExpressions.Regex]::Matches($origSDDL,'D:\w*\([\w\W]+\)')

        $newDACL = Get-SDDLfromJsonDACL -noDefaultAdminPermissions $noDefaultAdminPermissions -jsonDACL $jsonDACL

        $sddl = $origSDDL.Replace($origDACL,$newDACL)        
        try {
            Write-Verbose ('[SetPrinterDACL]Setting printer security descriptor in SDDL format: ' + $sddl)
            Set-Printer -Name $printerName -PermissionSDDL $sddl
        }
        catch {
            Write-Verbose '[SetPrinterDACL]Setting printer security descriptor failed'
            return $false
        }
    }
    # Use WMI to get/set DACL
    else
    {
        $origSDDL = Get-PrinterDACLWMI -printerName $printerName
        if ([System.String]::IsNullOrEmpty($origSDDL)) {
            Write-Verbose '[SetPrinterDACL]Reading current SDDL failed'
            return $false
        }

        $origDACL = [System.Text.RegularExpressions.Regex]::Matches($origSDDL,'D:\w*\([\w\W]+\)')
        $newDACL = Get-SDDLfromJsonDACL -noDefaultAdminPermissions $noDefaultAdminPermissions -jsonDACL $jsonDACL

        $sddl = $origSDDL.Replace($origDACL,$newDACL)
        Write-Verbose ('[SetPrinterDACL]Setting printer security descriptor in SDDL format: ' + $sddl)
        if (-not (Set-PrinterDACLWMI -printerName $printerName -desiredSDDL $sddl)) {
            Write-Verbose '[SetPrinterDACL]Setting printer security descriptor failed'
            return $false
        }
    }

    Write-Verbose '[SetPrinterDACL]Returning'
    return $true
}

# Updates DACL of the printer by using WMI classes (Windows Server 2008 / R2)
function Set-PrinterDACLWMI
{
    param(
        [Parameter(Mandatory=$true)]$printerName,
        [Parameter(Mandatory=$true)]$desiredSDDL
    )
    Write-Verbose '[Set-PrinterDACLWMI]Entering'

    # Parse SDDL and build new DACL
    $rawSD = New-Object System.Security.AccessControl.RawSecurityDescriptor($desiredSDDL) -ErrorAction SilentlyContinue
    if ($rawSD -eq $null) {
        Write-Verbose ('[Set-PrinterDACLWMI]Unable to parse SDDL: ' + $desiredSDDL)
        return $false
    }

    $newDACL = @()

    foreach ($ace in $rawSD.DiscretionaryAcl) {
        $newAce = ([WMIClass] "Win32_Ace").CreateInstance()
    
        $trustee = ([WMIClass] "Win32_Trustee").CreateInstance()
        $sid = $ace.SecurityIdentifier
        [byte[]] $sidArray = ,0 * $sid.BinaryLength
        $sid.GetBinaryForm($SIDArray,0)
        $trustee.SID = $sidArray

        $newAce.Trustee = $trustee
        $newAce.AccessMask = $ace.AccessMask
        $newAce.AceType = $ace.AceType
        $newAce.AceFlags = $ace.AceFlags

        $newDACL += $newAce
    }

    # Replace DACL in the original security descriptor
    $printer = Get-WmiObject -Class Win32_Printer -Filter ('name = ''' + $printerName + '''') -ErrorAction SilentlyContinue
    if ($printer -eq $null) {
        Write-Verbose ('[Set-PrinterDACLWMI]Printer not found: ' + $printerName)
        return $false
    }

    $sd = $printer.GetSecurityDescriptor().Descriptor
    $sd.DACL = $newDACL
    $sd.ControlFlags = 0x0004

    $printer.psbase.Scope.Options.EnablePrivileges = $true
    $result = $printer.SetSecurityDescriptor($sd)
    switch ($result.ReturnValue)
    {
        0 { Write-Verbose '[Set-PrinterDACLWMI]DACL successfully set' }
        2 { Write-Verbose '[Set-PrinterDACLWMI]Setting DACL failed. The user does not have access to the requested information.'; return $false }
        8 { Write-Verbose '[Set-PrinterDACLWMI]Setting DACL failed. Unknown failure.'; return $false }
        9 { Write-Verbose '[Set-PrinterDACLWMI]Setting DACL failed. The user does not have adequate privileges to execute the method.'; return $false }
        21 {Write-Verbose '[Set-PrinterDACLWMI]Setting DACL failed. A parameter specified in the method call is not valid.'; return $false }
        default { Write-Verbose '[Set-PrinterDACLWMI]Setting DACL failed.'; return $false }
    }

    return $true
}

# Parses DACL in JSON format and returns SDDL string
function Get-SDDLfromJsonDACL
{
    param(
        [Parameter(Mandatory=$true)][String]$jsonDACL,
        [Parameter(Mandatory=$false)][bool]$noDefaultAdminPermissions=$false
    )

    Write-Verbose '[Get-SDDLfromJsonDACL]Entering'
    $dacl = ConvertFrom-Json -InputObject $jsonDACL
    $disAcl = New-Object System.Security.AccessControl.DiscretionaryAcl($true,$false,0)

    foreach ($ace in $dacl) {
        $validAce = $true
        [System.Security.Principal.SecurityIdentifier]$sid = $null
        [System.Security.AccessControl.AceType]$accessType = 0
        [Int32]$accessMask = 0
        [System.Security.AccessControl.InheritanceFlags]$inheritanceFlags = [System.Security.AccessControl.InheritanceFlags]::None
        [System.Security.AccessControl.PropagationFlags]$propagationFlags = [System.Security.AccessControl.PropagationFlags]::None
             
        # Parse SID
        if ($ace.SecurityIdentifier) {
            $sid = New-Object System.Security.Principal.SecurityIdentifier($ace.SecurityIdentifier)
        }
        elseif ($ace.PrincipalName) {
            Write-Verbose ('[Get-SDDLfromJsonDACL]Converting PrincipalName to SID: ' + $ace.PrincipalName)
            try {
                $nt = New-Object System.Security.Principal.NTAccount($ace.PrincipalName)
                $sid = $nt.Translate([System.Security.Principal.SecurityIdentifier])
            }
            catch {
                Write-Verbose '[Get-SDDLfromJsonDACL]Converting PrincipalName to SID failed.'
                $validAce = $false
            }
        }
        else {
            Write-Verbose '[Get-SDDLfromJsonDACL]Missing SecurityIdentifier or PrincipalName key.'
            $validAce = $false
        }

        # Parse qualifier
        if ($ace.AccessType) {
            switch ($ace.AccessType.ToUpper())
            {
                'ALLOW' { $accessType = [System.Security.AccessControl.AccessControlType]::Allow }
                'DENY'  { $accessType = [System.Security.AccessControl.AccessControlType]::Deny }
                default { Write-Verbose ('[Get-SDDLAceList]Invalid AccessType value: ' + $ace.AccessType); $validAce = $false }
            }
        }
        else {
            Write-Verbose '[Get-SDDLfromJsonDACL]Missing AccessType key.'
            $validAce = $false
        }

        if (-not [System.String]::IsNullOrEmpty($ace.AccessPermissions)) {
            switch ($ace.AccessPermissions.ToUpper())
            {
                'PRINT'            { $permissions = 'SWRC' }
                'MANAGE DOCUMENTS' { $permissions = 'RPWPSDRCWDWO' }
                'MANAGE PRINTER'   { $permissions = 'LCSWSDRCWDWO' }
                default            { $permissions = $ace.AccessPermissions }                                
            }            

            try {
                $sd = New-Object System.Security.AccessControl.RawSecurityDescriptor(('D:(A;;'+$permissions+';;;SY)'))
                $accessMask = $sd.DiscretionaryAcl[0].AccessMask
            }
            catch {
                Write-Verbose ('[Get-SDDLfromJsonDACL]Invalid AccessPermissions value: ' + $ace.AccessPermissions)
                $validAce = $false
            }
        }
        else {
            Write-Verbose '[Get-SDDLfromJsonDACL]Missing AccessPermissions key.'
            $validAce = $false
        }

        if (-not [System.String]::IsNullOrEmpty($ace.AceFlags)) {
            try {
                $sd = New-Object System.Security.AccessControl.RawSecurityDescriptor(('D:(A;'+$ace.AceFlags+';GA;;;SY)'))
                $inheritanceFlags = $sd.DiscretionaryAcl[0].InheritanceFlags
                $propagationFlags = $sd.DiscretionaryAcl[0].PropagationFlags
            }
            catch {
                Write-Verbose ('[Get-SDDLfromJsonDACL]Invalid AceFlags value: ' + $ace.AceFlags)
                $validAce = $false
            }
        }

        if ($validAce) {
            Write-Verbose ('[Get-SDDLfromJsonDACL]Adding ACE: ' + $accessType.ToString() + ';' + $sid.Value + ';' + $accessMask.ToString() + ';' + $inheritanceFlags.ToString() + ';' + $propagationFlags.ToString())
            $disAcl.AddAccess($accessType,$sid,$accessMask,$inheritanceFlags,$propagationFlags)
        }
    }    
    
    Write-Verbose ('[Get-SDDLfromJsonDACL]Count of parsed ACEs: ' + $disAcl.Count.ToString())

    $ownerSID = [System.Security.Principal.NTAccount]::new('SYSTEM').Translate([System.Security.Principal.SecurityIdentifier])
    $groupSID = [System.Security.Principal.NTAccount]::new('Everyone').Translate([System.Security.Principal.SecurityIdentifier])    
    $commonSD = New-Object System.Security.AccessControl.CommonSecurityDescriptor($true,$false,[System.Security.AccessControl.ControlFlags]::None,$ownerSID,$groupSID,$null,$disAcl)
    
    $sddl = $commonSD.GetSddlForm([System.Security.AccessControl.AccessControlSections]::Access)

    # Append default admin permissions if not explicitly specified
    if (-not $noDefaultAdminPermissions) {
        # Built-in administrators
        if ([System.Text.RegularExpressions.Regex]::IsMatch($sddl,'\(*;BA\)') -eq $false) {
            $sddl += '(A;;LCSWSDRCWDWO;;;BA)'
        }
        
        # Local administrator
        if ([System.Text.RegularExpressions.Regex]::IsMatch($sddl,'\(*;LA\)') -eq $false) {
            $sddl += '(A;;LCSWSDRCWDWO;;;LA)'
        }

        # System account
        if ([System.Text.RegularExpressions.Regex]::IsMatch($sddl,'\(*;SY\)') -eq $false) {
            $sddl += '(A;;LCSWSDRCWDWO;;;SY)'
        }
    }

    Write-Verbose '[Get-SDDLfromJsonDACL]Returning'
    return $sddl
}    

<#
    This resoure manages print queues installed on the local computer. It enables to install/remove a print queue and configure its basic properties.
#>

[DscResource()]
class cPrinter
{
    # The name of the print queue
    [DscProperty(Key)]
    [String]$Name
    
    [DscProperty(Mandatory)]
    [Ensure] $Ensure    

    # The printer port managed through Win32_TcpIpPrinterPort WMI class.
    [DscProperty(Mandatory)]
    [string]$IPAddress
    
    # The name of the print driver.
    [DscProperty(Mandatory)]
    [string]$DriverName
    
    # The print server farm name (needs to be contained in the OptionalNames value in HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\LanManServer\Parameters)
    [DscProperty(Mandatory)]
    [string]$FarmName

    # If this property has a value then the print queue is shared under this name.
    [DscProperty()]
    [string]$ShareName

    # The location of the printer.
    [DscProperty()]
    [string]$Location

    # An additional comment displayed with the printer.
    [DscProperty()]
    [string]$Comment

    # Boolean value controlling that the print queue is not to be removed when the server is removed from the farm.
    [DscProperty()]
    [bool]$KeepWhenLeavingFarm
    
    #
    [DscProperty()]
    [bool]$Published
    
    # Boolean value controlling whether color printing is enabled or not
    [DscProperty()]
    [bool]$Color

    # String in JSON format specifying the DACL
    [DscProperty()]
    [String]$DaclInJSONFormat

    # Controls whether the resource adds default permissions to administrators or not
    [DscProperty()]
    [bool]$NoDefaultAdminPermissions=$false

    [void] Set()
    {
        Write-verbose '[Set] Entering'
        
        $farms = $this.GetLocalFarms()
        $this.FixProps()

        $Printer=Get-PrinterWMI -Name $this.Name -ErrorAction SilentlyContinue
        $PrintConfiguration=Get-PrintConfigurationWMI -Name $this.Name -ErrorAction SilentlyContinue
        $ColorConfiguration = $null
        [PrinterAction]$Action=[PrinterAction]::None
        
        if($this.Ensure -eq [Ensure]::Present)
        {
            [bool]$ShallCreatePrinter=$false
        
            if(($Farms -contains $this.FarmName) -and ($Printer -eq $null))
            {
                $Action=[PrinterAction]::Create
            }
            if(($Farms -notcontains $this.FarmName) -and ($Printer -ne $null) -and (-not $this.KeepWhenLeavingFarm))
            {
                $Action=[PrinterAction]::Remove
            }
        }
        else    #Absent
        {
            if(($Farms -contains $this.FarmName) -and ($Printer -ne $null))
            {
                $Action=[PrinterAction]::Remove
            }
        }
        switch($Action)
        {
            'Create'
            {
                if((Get-PrinterPortWMI -Name $this.IPAddress -ErrorAction SilentlyContinue) -eq $null)
                {
                    Write-Verbose '[Set] Port does not exist'
                    Add-PrinterPortWMI -Name $this.IPAddress -PrinterHostAddress $this.IPAddress
                }
                if((Get-PrinterDriverWMI -Name $this.DriverName -ErrorAction SilentlyContinue)-eq $null)
                {
                    Write-Verbose '[Set] Driver does not exist'
                    Add-PrinterDriverWMI -Name $this.DriverName
                }

                Add-PrinterWMI -DriverName $this.DriverName -Name $this.Name -PortName $this.IPAddress -Location $this.Location -Comment $this.Comment
                if(-not [string]::IsNullOrEmpty($this.ShareName))
                {
                    #create shared printer
                    Write-Verbose "[Set] Creating shared printer with name $($this.Name) and shared name $($this.ShareName)"
                    Set-printerWMI -Name $this.Name -Shared $true -ShareName $this.ShareName  -Published $this.Published
                }
                if(-not [string]::IsNullOrEmpty($this.Color))
                {
                    #set Color output mode for printing defaults
                    Write-Verbose "[Set] Setting Color Mode for printing defaults $($this.Name) to $($this.Color)"
                    Set-PrintConfigurationWMI -Name $this.Name -Color $this.Color
                }

                # Discreationary Access Control List (DACL)
                if(-not [String]::IsNullOrEmpty($this.DaclInJSONFormat))
                {
                    Set-PrinterDACL -printerName $this.Name -jsonDACL $this.DaclInJSONFormat -noDefaultAdminPermissions $this.NoDefaultAdminPermissions
                }

                break;
            }
            'Remove'
            {
                Write-Verbose '[Set] Printer should not exist -> removing'
                if($Printer -ne $null)
                {
                    Write-Verbose '[Set] Printer should not exist -> removing'
                    Remove-PrinterWMI -Name $this.Name
                }

                if((Get-PrinterPortWMI -Name $this.IPAddress -ErrorAction SilentlyContinue) -ne $null)
                {
                    Write-Verbose '[Set] Printer should not exist -> removing port as well'
                    Remove-PrinterPortWMI -Name $this.IPAddress
                }
                
                #we do not remove printer driver unless this was the last printer using it
                #if((Get-PrinterWMI | ?{$_.DriverName -eq $this.DriverName}) -eq $null)
                #{
                # Write-Verbose "[Set]Last printer using driver $($this.DriverName) -> removing driver"
                # Remove-PrinterDriverWMI -Name $this.DriverName
                #}
                break;
            }
            default
            {
                #keep status quo - just update properties if needed
                if($Printer -ne $null)
                {
                    if((-not $Printer.Shared) -and (-not [string]::IsNullOrWhiteSpace($this.ShareName)))
                    {
                        
                        Write-Verbose "[Set] Sharing the printer with share name $($this.ShareName)"
                        Set-PrinterWMI -Name $this.Name -Shared $true -ShareName $this.ShareName
                    }
                    if($Printer.Shared -and [string]::IsNullOrWhiteSpace($this.ShareName))
                    {
                        Write-Verbose "[Set] UnSharing the printer "
                        Set-PrinterWMI -Name $this.Name -Shared $false
                    }
                    if(-not [string]::IsNullOrWhiteSpace($this.Comment) -and ($Printer.Comment -ne $this.Comment))
                    {
                        Write-Verbose "[Set] Setting the printer description"
                        Set-PrinterWMI -Name $this.Name -Comment $this.Comment
                    }

                    if(-not [string]::IsNullOrWhiteSpace($this.Location) -and ($printer.Location -ne $this.Location))
                    {
                        Write-Verbose "[Set] Setting the printer location"
                        Set-PrinterWMI -Name $this.Name -Location $this.Location
                    }
                    if($Printer.Published -ne $this.Published)
                    {
                        Write-Verbose "[Set] Setting the printer publishing status"
                        Set-PrinterWMI -Name $this.Name -Published $this.Published
                    }
                    Switch ($PrintConfiguration.Color)
                    {
                        "1" {$ColorConfiguration = $false}
                        "2" {$ColorConfiguration = $true}
                        default {Write-Verbose "[Set] No match on Switch"}
                    }
                    if($ColorConfiguration -ne $this.Color)
                    {
                        Write-Verbose "[Set] Setting the printer output color mode"
                        Set-PrintConfigurationWMI -Name $this.Name -Color $this.Color
                    }

                    # Discreationary Access Control List (DACL)
                    if(-not [String]::IsNullOrEmpty($this.DaclInJSONFormat))
                    {
                        Set-PrinterDACL -printerName $this.Name -jsonDACL $this.DaclInJSONFormat -noDefaultAdminPermissions $this.NoDefaultAdminPermissions
                    }
                }
                break;
            }
        }
        Write-verbose '[Set] Returning'
    }
    
    [cPrinter]Get()
    {
        Write-verbose '[Get] Entering'

        $Printer=Get-PrinterWMI -Name $this.Name -ErrorAction SilentlyContinue
        # $PrintConfiguration=Get-PrintConfigurationWMI -Name $this.Name -ErrorAction SilentlyContinue
        if($Printer -eq $null)
        {
            Write-Verbose '[Get] Printer does not exist'
            $this.Ensure='Absent'
        }
        else
        {
            Write-Verbose '[Get] Printer exists'
            $this.Ensure='Present'
        }
        
        return $this
    }
    
    [bool]Test()
    {
        Write-verbose '[Test] Entering'
        
        $farms = $this.GetLocalFarms()
        $this.FixProps()

        [bool]$retVal=$false
        
        $Printer=Get-PrinterWMI -Name $this.Name -ErrorAction SilentlyContinue
        $PrintConfiguration=Get-PrintConfigurationWMI -Name $Printer.Name -ErrorAction SilentlyContinue
        New-Variable -Name ColorConfiguration
        if($this.Ensure -eq [Ensure]::Present)
        {
            if(($Printer -ne $null) -and ($farms -contains $this.FarmName)) #printer is there and server is member of the farm
            {
                $retVal=$true
                Write-Verbose "[Test] Present: Present AND InFarm"
            }
            if(($printer -eq $null) -and ($farms -notcontains $this.FarmName)) #server is not there and server is not member of the farm
            {
                $retVal=$true
                Write-Verbose "[Test] Present: NOT Present AND NOT InFarm"
            }
            if(($printer -ne $null) -and ($farms -notcontains $this.FarmName) -and ($this.KeepWhenLeavingFarm))
            {
                $retVal=$true
                Write-Verbose "[Test] Present: Present and NOT InFarm AND KeepeWhenLeavingFarm"
            }
            
            if($retVal)
            {
                if($Printer -ne $null)
                {
                    if($Printer.Location -ne $this.Location)
                    {
                        $retVal=$false
                        Write-Verbose "[Test] Present: PropTest: Location does not match"
                    }
                    if($Printer.Comment -ne $this.Comment)
                    {
                        $retVal=$false
                        Write-Verbose "[Test] Present: PropTest: Comment does not match"
                    }
                    if($printer.Shared)
                    {
                        if($printer.ShareName -ne $this.shareName)
                        {
                            $retVal=$false
                            Write-Verbose "[Test] Present: PropTest: ShareName does not match"
                        }
                        if([string]::IsNullOrWhiteSpace($this.ShareName))
                        {
                            $retVal=$false
                            Write-Verbose "[Test] Present: PropTest: We are shared but should not be"
                        }
                    }
                    if((-not $printer.Shared) -and (-not [string]::IsNullOrWhiteSpace($this.ShareName)))
                    {
                        $retVal=$false
                        Write-Verbose "[Test] Present: PropTest: We are not shared but should be"
                    }
                    if($Printer.Published -ne $this.Published)
                    {
                        $retVal=$false
                        Write-Verbose "[Test] Present: PropTest: Published does not match"
                    }
                    if($PrintConfiguration -ne $null)
                    {
                        Switch ($PrintConfiguration.Color)
                        {
                            "1" {$ColorConfiguration = $false}
                            "2" {$ColorConfiguration = $true}
                            default {Write-Verbose "[Test] Present: No Match on Switch"}
                        }
                        if ($ColorConfiguration -ne $this.Color)
                        {
                            Write-Verbose "[Test] Present: PropTest: Output Color does not match"
                            $retVal=$false
                        }
                    }
                }
            }
        }
        else    #Absent
        {
            if($Printer -eq $null)
            {
                $retVal = $true
                Write-Verbose "[Test] Absent: NOT Present"
            }
        }

        # Compare security descriptors
        if ($retVal -and ($this.Ensure -eq [Ensure]::Present) -and (-not [System.String]::IsNullOrEmpty($this.DaclInJSONFormat))) {
            Write-Verbose '[Test] Comparing DACL'
            $newSDDL = Get-SDDLfromJsonDACL -jsonDACL $this.DaclInJSONFormat -noDefaultAdminPermissions $this.NoDefaultAdminPermissions
            $oriSDDL = ''

            if (Get-PrintManagementAvailable) {
                $p = Get-Printer -Name $this.Name -Full -ErrorAction SilentlyContinue
                if ($p) {
                    $oriSDDL = $p.PermissionSDDL
                }
            }
            else {
                $oriSDDL = Get-PrinterDACLWMI -printerName $this.Name
            }

            Write-Verbose ('[Test] Present DACL: ' + $oriSDDL)
            Write-Verbose ('[Test] Desired DACL: ' + $newSDDL)
            $retVal = ([System.String]::Compare($newSDDL,$oriSDDL,$true) -eq 0)
        }

        Write-verbose "[Test] Returning: $retVal"
        return $retVal
    }
    
    [string[]]GetLocalFarms()
    {
        [Microsoft.Win32.RegistryKey]$key = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey("System\CurrentControlSet\Services\LanManServer\Parameters", $true)
        $farms = $key.GetValue("OptionalNames")
        
        #if value does not exist, use empty array
        if($farms -eq $null) {$farms=@()}
        return $farms
    }
    [void]FixProps()
    {
        if($this.Comment -eq [string]::Empty)
        {
            $this.Comment=$null
        }
        if($this.Location -eq [string]::Empty)
        {
            $this.Location=$null
        }
        if($this.ShareName -eq [string]::Empty)
        {
            $this.ShareName=$null
        }
    }
}
    
<#
    This resource enables to manage registry keys and values by means of importing a .reg file.
#>

[DscResource()]
class cRegistryUpdater
{
    # The fully qualified path to the .reg file
    [DscProperty(Key)]
    [string]$RegFilePath

    [DscProperty(Mandatory)]
    [Ensure] $Ensure

    [System.Array]$regKeys

    [cRegistryUpdater]Get()
    {
        Write-verbose '[Get] Entering'

        Write-verbose '[Get] Returning'
        return $this
    }
    
    [void]Set()
    {
        Write-Verbose '[Set] Entering'

        # Import registry file
        if ($this.Ensure -eq [Ensure]::Present)
        {
            $regFile = Get-Item -Path $this.RegFilePath -Force -ErrorAction SilentlyContinue
            if ($regFile)
            {
                $regFile = $regFile.FullName
                Write-Verbose ('[Set] Importing file: ' + $regFile)
                Start-Process -FilePath reg.exe -ArgumentList 'import',$regFile -NoNewWindow -Wait -ErrorAction SilentlyContinue
            }
            else
            {
                Write-Verbose ('[Set] Registry file not found: ' + $this.RegFilePath)
            }
        }
        # Delete all keys read from registry file
        else
        {
            if ($this.LoadRegFile($this.RegFilePath))
            {
                foreach ($k in $this.regKeys)
                {
                    $key = Get-Item -Path ('Registry::' + $k.Key) -Force -ErrorAction SilentlyContinue
                    if ($key)
                    {
                        Write-Verbose ('[Set] Deleting key: ' + $k.Key)
                        Remove-Item -Path $key.PSPath -Recurse -Force -ErrorAction SilentlyContinue
                    }
                    else
                    {
                        Write-Verbose ('[Set] Key does not exist: ' + $k.Key)
                    }
                }
            }
            else
            {
                Write-Verbose ('[Set] Reading registry file failed: ' + $this.RegFilePath)
            }
        }

        Write-Verbose '[Set] Returning'
    }

    [bool]Test()
    {        
        [bool]$retVal = $false

        # Registry file loaded
        if ($this.LoadRegFile($this.RegFilePath))
        {
            if ($this.Ensure -eq [Ensure]::Present)
            {
                $retVal = $this.TestRegPresent()        
            }
            else
            {
                $retVal = $this.TestRegAbsent()
            }

            return $retVal
        }
        # Loading registry file failed
        else 
        { 
            return $retVal #FALSE
        }
    }

    # Validates registry entries: [Ensure]::Present
    [bool]TestRegPresent()
    {
        Write-Verbose '[TestRegPresent] Entering'

        foreach ($k in $this.regKeys)
        {
            $key = Get-Item -Path ('Registry::' + $k.Key) -Force -ErrorAction SilentlyContinue
            if (($k.Operator -eq '') -and ($key -eq $null))
            {
                Write-Verbose ('[TestRegPresent] Key not found: ' + $k.Key)
                return $false
            }

            if (($k.Operator -eq '-') -and ($key -ne $null))
            {
                Write-Verbose ('[TestRegPresent] Key exists: ' + $k.Key)
                return $false
            }
        }

        Write-Verbose '[TestRegPresent] Returning'
        return $true
    }

    # Validates registry entries: [Ensure]::Absent
    [bool]TestRegAbsent()
    {
        Write-Verbose '[TestRegAbsent] Entering'

        foreach ($k in $this.regKeys)
        {
            $key = Get-Item -Path ('Registry::' + $k.Key) -Force -ErrorAction SilentlyContinue
            if ($key -ne $null)
            {
                Write-Verbose ('[TestRegAbsent] Key exists: ' + $k.Key)
                return $false
            }
        }

        Write-Verbose '[TestRegAbsent] Returning'
        return $true
    }
    
    # Loads registry file and imports keys
    [bool]LoadRegFile([String]$regFile)
    {
        $this.regKeys = @()

        Write-Verbose "[LoadRegFile] Checking registry file: $regFile"
        $f = Get-Item -Path $regFile -ErrorAction SilentlyContinue
        if ($f -eq $null) { return $false }

        $i = 0
        $curKey = ''

        foreach ($row in (Get-Content -Path $regFile -Force -Encoding Unicode -ErrorAction SilentlyContinue)) 
        {
            if ([System.Text.RegularExpressions.Regex]::IsMatch($row,'^\[\-?HK[\w|\W]+\]$')) 
            {                
                $curKey = [System.Text.RegularExpressions.Regex]::Match($row,'(?<=^\[\-?)HK[\w|\W]+(?=\]$)').Value
                
                if ($curKey -ne "") 
                { 
                    $key = @{ Key = $curKey; Operator = [System.Text.RegularExpressions.Regex]::Match($row,'(?<=\[)\-').Value }
                    $this.regKeys += $key
                    $i++
                    Write-Verbose "[LoadRegFile] Read key (#$i): $curKey"
                }           
            }    
        }    

        Write-Verbose '[LoadRegFile] Returning'
        return $true
    }
}