Category Archives: Microsoft

Artikels die een relatie hebben met Microsoft producten

SHA2 certificate OpenSSL Windows

If you have Windows and an OpenSSL installation you can request a SHA256 certificate using the followin code:

set domein=YourDomain
set pass=YourPassword
set organisatie=YourOrganisation
set provincie=YourProviceorState
set stad=YourCity
set string="/CN=%domein%/O=%organisatie%/C=NL/ST=%provincie%/L=%stad%"
echo %string%
openssl genrsa -des3 -passout pass:%pass% 2048 > %domein%.key
openssl rsa -in %domein%.key -passin pass:%pass% -out %domein%-decrypted.key
openssl req –sha256 -new -key %domein%-decrypted.key -subj %string% -out %domein%.csr

Encrypt values using AES RSA in Powershell

You can encrypt a password so that only under your account it can be decrypted. If however you need some other user to decrypt the data the code is worthless. You can use different code for this case. You need a certificate with private key. It can be a certificate that is self signed. You can use byte values of a maximum van 32 (equals 256 bit). I would recommend using a 32 Byte value. Get the thumbprint value using the following code:

Get-Item -Path Cert:\CurrentUser\My\*

Of course you can also use a certificate that is installed on the local machine. For the sake of better security I would use a certificate that is installed in the CurrentUser part of the certificate store.

If you know the thumbprint of the certificate, state that value in the $thumbprint string. Enter the plaintext value which you want to encrypt in the string $Securestring. Configure the Export-Clixml path to you liking. You can change the name of the XML file without voiding the abillity to decrypt.

try
{
    $secureString = 'StrongPassword' | 
                    ConvertTo-SecureString -AsPlainText -Force

    # Generate our new 32-byte AES key.  I don't recommend using Get-Random for this; the System.Security.Cryptography namespace
    # offers a much more secure random number generator.

    $key = New-Object byte[](32)
    $rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::Create()

    $rng.GetBytes($key)

    $encryptedString = ConvertFrom-SecureString -SecureString $secureString -Key $key

    # This is the thumbprint of a certificate on my test system where I have the private key installed.Note that the thumbprint value is CASE SENSITIVE

    $thumbprint = 'D180EA99616A925E716278635E29159BC7105663'
    $cert = Get-Item -Path Cert:\CurrentUser\My\$thumbprint -ErrorAction Stop

    $encryptedKey = $cert.PublicKey.Key.Encrypt($key, $true)

    $object = New-Object psobject -Property @{
        Key = $encryptedKey
        Payload = $encryptedString
    }

    $object | Export-Clixml .\encryptionTest2.xml

}
finally
{
    if ($null -ne $key) { [array]::Clear($key, 0, $key.Length) }

You can decrypt the value securely using the following code:

try
{
    $object = Import-Clixml -Path .\encryptionTest2.xml

    $thumbprint = 'D180EA99616A925E716278635E29159BC7105663'
    $cert = Get-Item -Path Cert:\CurrentUser\My\$thumbprint -ErrorAction Stop

    $key = $cert.PrivateKey.Decrypt($object.Key, $true)

    $secureString = $object.Payload | ConvertTo-SecureString -Key $key 
	
}
finally
{
    if ($null -ne $key) { [array]::Clear($key, 0, $key.Length) }
}

$cred = New-Object System.Management.Automation.PSCredential(Useraccount, $secureString)

Now you can run command’s under an account without having to compromise the password easily. You need to have the certificate to decrypt.  Don’t try to encrypt the username also. Powershell does not accept secure usernames somehow.  An example of how to use this would be:

Import-Module ActiveDirectory
$Username = [Environment]::UserName
Get-ADUser -Identity $Username -Credential $Cred

To troubleshoot you can decrypt the password in plain text using the following code. Please note that this leaves the unencrypted password in the memory waiting to be exploited.

$cred = New-Object System.Management.Automation.PSCredential('Username', $secureString)
$plainText = $cred.GetNetworkCredential().Password
Write-Host $Plaintext

Encrypt Values using DPAPI

You can encrypt passwords for your account only using DPAPI. You need the function on this site.

Import the module. On some hardened systems using:

Import-Module '.\Modulename.psm1' -Force

Next encrypt the password using the following command:

$secureString = Read-Host -AsSecureString "Enter a secret password." 

# You can pass basically anything as the Entropy value, but I'd recommend sticking to simple value types (including strings),
# or arrays of those types, to make sure that the binary serialization of your entropy object doesn't change between
# script executions.  Here, we'll use Pi.  Edit:  The latest version of the code enforces the use of the recommended simple
# types, unless you also use the -Force switch.

$secureString | ConvertFrom-SecureString -Entropy ([Math]::PI) | Out-File .\storedPassword.txt

You can use it by decrypting the password as a secure string using the following code:

$newSecureString = Get-Content -Path .\storedPassword.txt | ConvertTo-SecureString -Entropy ([Math]::PI)

You can decrypt the password as plain text using the following code:

$newSecureString = Get-Content -Path .\storedPassword.txt | ConvertTo-SecureString -Entropy ([Math]::PI)
$newSecureString | ConvertFrom-SecureString -AsPlainText -Force

Function for securing passwords with DPAPI

DPAPI is a encryptionprofile that prevents any other process to access data. Only your user account can decrypt the data. Also only on the computer where you encrypted to password on. If you have a roaming profile you don’t have this issue. What administrator use a roaming profile!?

PSM1:

requires -Version 2.0

if ($PSVersionTable.PSVersion.Major -ge 3)
{
    Import-Module Microsoft.PowerShell.Security -Global
}

$script:validEntropyTypes = @(
    [System.ValueType]
    [string]
    [System.Security.SecureString]
    [System.Text.StringBuilder]
)

function ConvertTo-SecureString
{
    <#
    
    .ForwardHelpTargetName ConvertTo-SecureString
    .ForwardHelpCategory Cmdlet
    
    #>

    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [string]
        ${String},
    
        [Parameter(ParameterSetName='PlainText', Position=1)]
        [switch]
        ${AsPlainText},
    
        [Parameter(ParameterSetName='PlainText', Position=2)]
        [Parameter(ParameterSetName = 'WithEntropy')]
        [switch]
        ${Force},
    
        [Parameter(ParameterSetName='Secure', Position=1)]
        [System.Security.SecureString]
        ${SecureKey},
    
        [Parameter(ParameterSetName='Open')]
        [byte[]]
        ${Key},

        [Parameter(ParameterSetName = 'WithEntropy')]
        [Object]
        $Entropy
    )
    
    begin
    {
        Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

        if ($PSCmdlet.ParameterSetName -eq 'WithEntropy')
        {
            try
            {
                $entropyBytes = Get-EntropyBytes -Entropy $Entropy -Force:$Force
            }
            catch
            {
                throw
            }
        }
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'WithEntropy'
            {
                Add-Type -AssemblyName 'System.Security' -ErrorAction Stop

                try
                {
                    $encryptedBytes = Get-ByteArrayFromString -String $String
                    $plainTextBytes = [System.Security.Cryptography.ProtectedData]::Unprotect($encryptedBytes, $entropyBytes, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)
                    $plainTextChars = [System.Text.Encoding]::Unicode.GetChars($plainTextBytes)

                    $secureString = New-Object System.Security.SecureString

                    foreach ($char in $plainTextChars)
                    {
                        $secureString.AppendChar($char)
                    }

                    $secureString.MakeReadOnly()

                    return $secureString
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $plainTextChars) { [array]::Clear($plainTextChars, 0, $plainTextChars.Count) }
                    if ($null -ne $plainTextBytes) { [array]::Clear($plainTextBytes, 0, $plainTextBytes.Count) }
                    if ($null -ne $entropyBytes)   { [array]::Clear($entropyBytes, 0, $entropyBytes.Count) }
                }
                
                break
            }

            default
            {
                try
                {
                    $cmd = Get-Command -Name ConvertTo-SecureString -CommandType Cmdlet
                    return & $cmd @PSBoundParameters
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                
                break
            }
        }

    } # process
    
} # function ConvertTo-SecureString

function ConvertFrom-SecureString
{
    <#
    
    .ForwardHelpTargetName ConvertFrom-SecureString
    .ForwardHelpCategory Cmdlet
    
    #>

    [CmdletBinding(DefaultParameterSetName='Secure')]
    param(
        [Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateScript({ $_.Length -gt 0 })]
        [System.Security.SecureString]
        ${SecureString},
    
        [Parameter(ParameterSetName='Secure', Position=1)]
        [System.Security.SecureString]
        ${SecureKey},
    
        [Parameter(ParameterSetName='Open')]
        [byte[]]
        ${Key},

        [Parameter(ParameterSetName = 'WithEntropy')]
        [Object]
        $Entropy,

        [Parameter(ParameterSetName = 'PlainText')]
        [switch]
        $AsPlainText,

        [Parameter(ParameterSetName = 'PlainText')]
        [Parameter(ParameterSetName = 'WithEntropy')]
        [switch]
        $Force
    )
    
    begin
    {
        Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

        if ($PSCmdlet.ParameterSetName -eq 'WithEntropy')
        {
            Add-Type -AssemblyName 'System.Security'

            try
            {
                $entropyBytes = Get-EntropyBytes -Entropy $Entropy -Force:$Force
            }
            catch
            {
                throw
            }
        }
        elseif ($PSCmdlet.ParameterSetName -eq 'PlainText' -and -not $Force)
        {
            throw 'The system cannot protect plain text output.  To suppress this warning and convert the SecureString to plain text, reissue the command specifying the Force parameter.'
        }
    }

    process
    {
        switch ($PSCmdlet.ParameterSetName)
        {
            'PlainText'
            {
                try
                {
                    $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secureString)
                    return [System.Runtime.InteropServices.Marshal]::PtrToStringUni($ptr)
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
                }
                
                break
            }

            'WithEntropy'
            {
                try
                {
                    $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($secureString)
                    $chars = New-Object Char[]($secureString.Length)
                    [System.Runtime.InteropServices.Marshal]::Copy($ptr, $chars, 0, $secureString.Length)
                    $bytes = [System.Text.Encoding]::Unicode.GetBytes($chars)
                    $encryptedBytes = [System.Security.Cryptography.ProtectedData]::Protect($bytes, $entropyBytes, [System.Security.Cryptography.DataProtectionScope]::CurrentUser)

                    return Get-StringFromByteArray -ByteArray $encryptedBytes
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                finally
                {
                    if ($null -ne $ptr)          { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
                    if ($null -ne $chars)        { [array]::Clear($chars, 0, $chars.Count) }
                    if ($null -ne $bytes)        { [array]::Clear($bytes, 0, $bytes.Count) }
                    if ($null -ne $entropyBytes) { [array]::Clear($entropyBytes, 0, $entropyBytes.Count) }
                }
                
                break
            }

            default
            {
                try
                {
                    $cmd = Get-Command -Name ConvertFrom-SecureString -CommandType Cmdlet
                    return & $cmd @PSBoundParameters
                }
                catch
                {
                    Write-Error -ErrorRecord $_
                }
                
                break
            }
        }

    } # process

} # function ConvertTo-SecureString

function Get-ByteArrayFromString
{
    # Converts a string containing an even number of hexadecimal characters into a byte array.

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({
            # Could use ValidatePattern for this, but ValidateScript allows for a more user-friendly error message.
            if ($_ -match '[^0-9A-F]')
            {
                throw 'String must only contain hexadecimal characters (0-9 and A-F).'
            }

            if ($_.Length % 2 -ne 0)
            {
                throw 'String must contain an even number of characters'
            }

            return $true
        })]
        [string]
        $String
    )

    $length = $String.Length / 2
    $bytes = New-Object byte[]($length)

    for ($i = 0; $i -lt $length; $i++)
    {
        $bytes[$i] = [byte]::Parse($String.Substring($i * 2, 2), [Globalization.NumberStyles]::AllowHexSpecifier, [Globalization.CultureInfo]::InvariantCulture)
    }

    return ,$bytes
}

function Get-EntropyBytes
{
    [CmdletBinding()]
    param (
        $Entropy,

        [switch]
        $Force
    )

    if ($null -eq $Entropy)
    {
        return $null
    }
    elseif (-not $Force -and -not (Test-EntropyType -Object $Entropy))
    {
        throw @'
Entropy object's type should be a value type, string, StringBuilder, SecureString, or an array containing only value types or strings.
Use of other object types might result in a different binary representation of the object between script executions.  To suppress this message and use any type of entropy object, use the -Force switch.
'@
    }

    if ($Entropy -is [byte[]])
    {
        # Clone the object because the caller may be zeroing out the byte array when they're done, due
        # to it potentially containing plain text data from a SecureString.

        return $Entropy.Clone()
    }
    elseif ($Entropy -is [string])
    {
        return [System.Text.Encoding]::Unicode.GetBytes($Entropy)
    }
    elseif ($Entropy -is [System.Text.StringBuilder])
    {
        return [System.Text.Encoding]::Unicode.GetBytes($Entropy.ToString())
    }
    elseif ($Entropy -is [System.Security.SecureString])
    {
        try
        {
            $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode($Entropy)
            $chars = New-Object Char[]($Entropy.Length)

            [System.Runtime.InteropServices.Marshal]::Copy($ptr, $chars, $Entropy.Length)

            return [System.Text.Encoding]::Unicode.GetBytes($chars)
        }
        catch
        {
            throw
        }
        finally
        {
            if ($null -ne $ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeGlobalAllocUnicode($ptr) }
            if ($null -ne $chars) { [Array]::Clear($chars) }
        }
    }
    else
    {
        try
        {
            $ms = New-Object System.IO.MemoryStream
            $bf = New-Object System.Runtime.Serialization.Formatters.Binary.BinaryFormatter

            $bf.Serialize($ms, $Entropy)
            
            return ,$ms.ToArray()
        }
        catch
        {
            throw
        }
        finally
        {
            if ($null -ne $ms) { $ms.Dispose() }
        }
    }
}

function Get-StringFromByteArray
{
    # Converts byte array into a string of hexadecimal characters in the same order as the byte array

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [byte[]]
        $ByteArray
    )

    $sb = New-Object System.Text.StringBuilder

    for ($i = 0; $i -lt $ByteArray.Length; $i++)
    {
        $null = $sb.Append($ByteArray[$i].ToString('x2', [Globalization.CultureInfo]::InvariantCulture))
    }

    return $sb.ToString()
}

function Test-EntropyType
{
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        $Object
    )

    if ($Object -is [array])
    {
        foreach ($obj in $Object)
        {
            if ($obj -isnot [System.ValueType] -and $obj -isnot [string])
            {
                return $false
            }
        }

        return $true
    }
    else
    {
        foreach ($type in $script:validEntropyTypes)
        {
            if ($Object -is $type)
            {
                return $true
            }
        }

        return $false
    }
}

function Get-CallerPreference
{
    <#
    .Synopsis
       Fetches "Preference" variable values from the caller's scope.
    .DESCRIPTION
       Script module functions do not automatically inherit their caller's variables, but they can be
       obtained through the $PSCmdlet variable in Advanced Functions.  This function is a helper function
       for any script module Advanced Function; by passing in the values of $ExecutionContext.SessionState
       and $PSCmdlet, Get-CallerPreference will set the caller's preference variables locally.
    .PARAMETER Cmdlet
       The $PSCmdlet object from a script module Advanced Function.
    .PARAMETER SessionState
       The $ExecutionContext.SessionState object from a script module Advanced Function.  This is how the
       Get-CallerPreference function sets variables in its callers' scope, even if that caller is in a different
       script module.
    .PARAMETER Name
       Optional array of parameter names to retrieve from the caller's scope.  Default is to retrieve all
       Preference variables as defined in the about_Preference_Variables help file (as of PowerShell 4.0)
       This parameter may also specify names of variables that are not in the about_Preference_Variables
       help file, and the function will retrieve and set those as well.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Imports the default PowerShell preference variables from the caller into the local scope.
    .EXAMPLE
       Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState -Name 'ErrorActionPreference','SomeOtherVariable'

       Imports only the ErrorActionPreference and SomeOtherVariable variables into the local scope.
    .EXAMPLE
       'ErrorActionPreference','SomeOtherVariable' | Get-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState

       Same as Example 2, but sends variable names to the Name parameter via pipeline input.
    .INPUTS
       String
    .OUTPUTS
       None.  This function does not produce pipeline output.
    .LINK
       about_Preference_Variables
    #>

    [CmdletBinding(DefaultParameterSetName = 'AllVariables')]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateScript({ $_.GetType().FullName -eq 'System.Management.Automation.PSScriptCmdlet' })]
        $Cmdlet,

        [Parameter(Mandatory = $true)]
        [System.Management.Automation.SessionState]
        $SessionState,

        [Parameter(ParameterSetName = 'Filtered', ValueFromPipeline = $true)]
        [string[]]
        $Name
    )

    begin
    {
        $filterHash = @{}
    }
    
    process
    {
        if ($null -ne $Name)
        {
            foreach ($string in $Name)
            {
                $filterHash[$string] = $true
            }
        }
    }

    end
    {
        # List of preference variables taken from the about_Preference_Variables help file in PowerShell version 4.0

        $vars = @{
            'ErrorView' = $null
            'FormatEnumerationLimit' = $null
            'LogCommandHealthEvent' = $null
            'LogCommandLifecycleEvent' = $null
            'LogEngineHealthEvent' = $null
            'LogEngineLifecycleEvent' = $null
            'LogProviderHealthEvent' = $null
            'LogProviderLifecycleEvent' = $null
            'MaximumAliasCount' = $null
            'MaximumDriveCount' = $null
            'MaximumErrorCount' = $null
            'MaximumFunctionCount' = $null
            'MaximumHistoryCount' = $null
            'MaximumVariableCount' = $null
            'OFS' = $null
            'OutputEncoding' = $null
            'ProgressPreference' = $null
            'PSDefaultParameterValues' = $null
            'PSEmailServer' = $null
            'PSModuleAutoLoadingPreference' = $null
            'PSSessionApplicationName' = $null
            'PSSessionConfigurationName' = $null
            'PSSessionOption' = $null

            'ErrorActionPreference' = 'ErrorAction'
            'DebugPreference' = 'Debug'
            'ConfirmPreference' = 'Confirm'
            'WhatIfPreference' = 'WhatIf'
            'VerbosePreference' = 'Verbose'
            'WarningPreference' = 'WarningAction'
        }


        foreach ($entry in $vars.GetEnumerator())
        {
            if (([string]::IsNullOrEmpty($entry.Value) -or -not $Cmdlet.MyInvocation.BoundParameters.ContainsKey($entry.Value)) -and
                ($PSCmdlet.ParameterSetName -eq 'AllVariables' -or $filterHash.ContainsKey($entry.Name)))
            {
                $variable = $Cmdlet.SessionState.PSVariable.Get($entry.Key)
                
                if ($null -ne $variable)
                {
                    if ($SessionState -eq $ExecutionContext.SessionState)
                    {
                        Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force
                    }
                    else
                    {
                        $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                    }
                }
            }
        }

        if ($PSCmdlet.ParameterSetName -eq 'Filtered')
        {
            foreach ($varName in $filterHash.Keys)
            {
                if (-not $vars.ContainsKey($varName))
                {
                    $variable = $Cmdlet.SessionState.PSVariable.Get($varName)
                
                    if ($null -ne $variable)
                    {
                        if ($SessionState -eq $ExecutionContext.SessionState)
                        {
                            Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force
                        }
                        else
                        {
                            $SessionState.PSVariable.Set($variable.Name, $variable.Value)
                        }
                    }
                }
            }
        }

    } # end

} # function Get-CallerPreference


Export-ModuleMember -Function 'ConvertTo-SecureString', 'ConvertFrom-SecureString', 'Get-CallerPreference'

ZIP:

ConvertTo-SecureFunctions

Convert String to Scriptblock Function

You cannot invoke-command a string. You should use a Scriptblock. If you import a value as a string you can convert it only if it is a single value string using the following command:

[scriptblock]::Create("$string")

To convert an multivalue array to scriptblock you can use a module as shown below:

function Convert-StringToScriptBlock {
param(
[parameter(ValueFromPipeline=$true,Position=0)]
[string]
$string
)
$sb = [scriptblock]::Create($string)
return $sb
}

Export-ModuleMember -Function 'Convert-StringToScriptBlock'

ZIP:

Convert-StringtoScripblock

Import PFX Certificate Module

Windows Server 2012 R2 and Windows 8.1 contain a nice function for import-PFXCertificate. However if you mange an older operating system you can use a different function:

function Import-PfxCertificate {
 
param([String]$certPath,[String]$certRootStore = “CurrentUser”,[String]$certStore = “My”,$pfxPass = $null)
$pfx = new-object System.Security.Cryptography.X509Certificates.X509Certificate2
 
if ($pfxPass -eq $null) {$pfxPass = read-host “Enter the pfx password” -assecurestring}
 
$pfx.import($certPath,$pfxPass,“Exportable,PersistKeySet”)

$store = new-object System.Security.Cryptography.X509Certificates.X509Store($certStore,$certRootStore)
$store.open(“MaxAllowed”)
$store.add($pfx)
$store.close()
}
Export-ModuleMember -Function 'Import-PfxCertificate'

Very basic hiding of words

Imagine a case where some user should fire up a script. You can specify credentials in plain text. However in some cases you shouldn’t want this. You can shuffle letters a bit so that an user cannot see the plain text password. You shouldn’t use this for anything higer than a low profile user account. Let’s say this is a test case.

Shuffle letters from CHAR To BYTE:

$Data = "Welkom01"
$Data=[CHAR[]]$data
$Pass = @()
Foreach ($letter in $Data){
$Pass += ([BYTE][CHAR]"$letter") 
}
write-host $pass -Separator " " -NoNewline

This will output 87 101 108 107 111 109 48 49

Shuffle letters from BYTE To CHAR:

$Data = "87 101 108 107 111 109 48 49"
$Data = $Data.Split(" ")
$Pass = @()
Foreach ($Item in $data) {$Pass += ([CHAR][BYTE]"$Item")}

$pass -join""

 

 

 

 

Suprise woman with days anniversary

Powershell can calculate basically everything. If you want the suprise someone with a number of days anniversary you can run the following code:

#Current date
$1 = get-date
#Old date
$2 = get-date 31/12/2009

($1 - $2).days

<#Something is returned. You can calculate to difference eq:
PS > 1561

Just run this code. It is as simple as a calculator
2000 - 1561
PS>439
#>

$1.AddDays(439)

Add Printer Script Unique Print Drivers

For an automated installation it was necessary to have an up to date list of the printer drivers. As print manufactorers occassionally add a dozen printer drivers into one setup we wanted to have a script that connects to all on duty print servers. I used the function Add-PrinterDriver for this cause. In the RES Automation Manager script there was some code necessary to first add a feature if it wasn’t present:

Import-Module ServerManager
$RSATADPowerShell = Get-WindowsFeature -Name "RSAT-AD-PowerShell"
If ($RSATADPowerShell.Installed -match "False"){
Add-WindowsFeature RSAT-AD-PowerShell}

Then I added the function in another script:

Import-Module ActiveDirectory
#Functie Add-PrinterDriver
function Add-PrinterDriver { 
 [CmdletBinding()] 
         Param( 
              [Parameter(Mandatory=$true)] 
              [string] $PrintServer, 
              [switch] $Clean 
             ) 

#Collecting all shared printer objects from the specified print server 
$allprinters = @(Get-WmiObject win32_printer -ComputerName $PrintServer -Filter 'shared=true') 
#Defining all unique printer drivers from the specified print server 
$drivers = @($allprinters | Select-Object drivername -Unique) 
#Defining a collection containing the first printer object using a unique printer driver 
$printers = @() 
foreach ($item in $drivers){ 
$printers += @($allprinters | Where-Object {$_.drivername -eq $item.drivername})[0] 
} 

#Collecting locally installed drivers 
$localdrivers = @() 
foreach ($driver in (Get-WmiObject Win32_PrinterDriver)){ 
$localdrivers += @(($driver.name -split ",")[0]) 
} 

#Initializing the CurrentPrinter variable for use with Write-Progress 
$CurrentPrinter = 1 

#Looping through the printer objects collection, installing those who are not already installed on the local computer 
foreach ($printer in $printers) { 

Write-Progress -Activity "Installing printers..." -Status "Current printer: $($printer.name)" -Id 1 -PercentComplete (($CurrentPrinter/$printers.count) * 100) 

#Create hash-table for output object 
$outputobject = @{} 
$outputobject.drivername = $printer.drivername 

$locallyinstalled = $localdrivers | Where-Object {$_ -eq $printer.drivername} 
if (-not $locallyinstalled) { 
Write-Verbose "$($printer.drivername) is not installed locally" 
$AddPrinterConnection = Invoke-WmiMethod -Path Win32_Printer -Name AddPrinterConnection -ArgumentList ([string]::Concat('\', $printer.__SERVER, '', $printer.ShareName)) -EnableAllPrivileges 
$outputobject.returncode = $AddPrinterConnection.ReturnValue 
} 
else 
{ 
Write-Verbose "$($printer.drivername) is already installed locally" 
$outputobject.returncode = "Already installed" 
} 

#Create a new object for each driver, based on the outputobject hash-table 
New-Object -TypeName PSObject -Property $outputobject 

$CurrentPrinter ++ 

} 

#Deletes all printer connections for the current user 
if ($clean) { 
$printers = Get-WmiObject Win32_Printer -EnableAllPrivileges -Filter network=true 
if ($printers) { 
foreach ($printer in $printers) { 
$printer.Delete() 
} 
} 
} 
}

$OS = (Get-WmiObject Win32_OperatingSystem).Caption.replace("Microsoft ","").Replace("Standard ","Standard").Replace("Enterprise ","Enterprise").Replace("Datacenter ","Datacenter")
$ADPrintservers = Get-ADComputer -Filter * -Property * |Where {$_.name -like "PR*"} | select DNSHostName,OperatingSystem
$Pingable =@()
foreach ($computer in $ADPrintservers){
if (Test-Connection $Computer.DNSHostName -quiet) { $Pingable += $Computer}}
foreach ($Computer in $Pingable){
If ($OS -contains $Computer.OperatingSystem){
Add-PrinterDriver -Printserver $Computer.DNSHostName}}

The script above enumerates all servers starting with PR*. Next this script test whether the server is pingable or not. You could enter credentials if you disabled pinging to the Test-Connection command. Next it validates if the server has the same OS as the print server. Some print servers are around for the 32 bit environment. You can also add a filter for that. At the end it deletes the space that is somehow added to the replace part of OS variable.  The bare function is like this:

function Add-PrinterDriver { 
 [CmdletBinding()] 
         Param( 
              [Parameter(Mandatory=$true)] 
              [string] $PrintServer, 
              [switch] $Clean 
             ) 

#Collecting all shared printer objects from the specified print server 
$allprinters = @(Get-WmiObject win32_printer -ComputerName $PrintServer -Filter 'shared=true') 
#Defining all unique printer drivers from the specified print server 
$drivers = @($allprinters | Select-Object drivername -Unique) 
#Defining a collection containing the first printer object using a unique printer driver 
$printers = @() 
foreach ($item in $drivers){ 
$printers += @($allprinters | Where-Object {$_.drivername -eq $item.drivername})[0] 
} 

#Collecting locally installed drivers 
$localdrivers = @() 
foreach ($driver in (Get-WmiObject Win32_PrinterDriver)){ 
$localdrivers += @(($driver.name -split ",")[0]) 
} 

#Initializing the CurrentPrinter variable for use with Write-Progress 
$CurrentPrinter = 1 

#Looping through the printer objects collection, installing those who are not already installed on the local computer 
foreach ($printer in $printers) { 

Write-Progress -Activity "Installing printers..." -Status "Current printer: $($printer.name)" -Id 1 -PercentComplete (($CurrentPrinter/$printers.count) * 100) 

#Create hash-table for output object 
$outputobject = @{} 
$outputobject.drivername = $printer.drivername 

$locallyinstalled = $localdrivers | Where-Object {$_ -eq $printer.drivername} 
if (-not $locallyinstalled) { 
Write-Verbose "$($printer.drivername) is not installed locally" 
$AddPrinterConnection = Invoke-WmiMethod -Path Win32_Printer -Name AddPrinterConnection -ArgumentList ([string]::Concat('\', $printer.__SERVER, '', $printer.ShareName)) -EnableAllPrivileges 
$outputobject.returncode = $AddPrinterConnection.ReturnValue 
} 
else 
{ 
Write-Verbose "$($printer.drivername) is already installed locally" 
$outputobject.returncode = "Already installed" 
} 

#Create a new object for each driver, based on the outputobject hash-table 
New-Object -TypeName PSObject -Property $outputobject 

$CurrentPrinter ++ 

} 

#Deletes all printer connections for the current user 
if ($clean) { 
$printers = Get-WmiObject Win32_Printer -EnableAllPrivileges -Filter network=true 
if ($printers) { 
foreach ($printer in $printers) { 
$printer.Delete() 
} 
} 
} 
}

This function lets powershell connect to the printserver. It does a query on all shared printers. Then it sorts the results to unique values. At last it connects to that printer which causes the driver to be installed. There is a group policy necessary for this to work. The following policy settings are mandatory:

PointAndPrintRestrictions

 

This is a computer policy located at Computer Configuration –> Windows Settings –> Printers –> Point and Print Restrictions