Tag Archives: Scripting

Scripting printer permissions

I will probably need to refer to this myself, but maybe someone else will find this useful.

Still working through some print queue management tasks, I needed to change permissions on a collection of printers. Yesterday, I broke down and did a bunch of repetitive work in the GUI, and noticed there were some inconsistencies in the permissions applied to a clients print queues. Today, I decided that I would fix them, but I didn’t want to use the GUI.

Although the Server 2012 Printer Management Set-Printer cmdlet can change the permissions through its -PermissionSDDL parameter, I haven’t found a workable (even if you call SDDL workable) way to do so on a Server 2008 system.

So I fell back to using PowerShell with Helge Klein’s SetACL command-line tool. I could also have used his COM version if I wanted to be really efficient. I use an array of arguments to the external command. See this Scripting Guy post for more info on calling external commands.

Here’s what I did:

# Retrieve a list of target printers
PS C:> $rlprinters = gwmi Win32_Printer -filter "Name like 'RL -%' OR Name like 'RL-%' "

# Construct a list of printer names in \\[hostname]\[sharename] format
PS C:> $printers = $rlprinters | % { '\\' + $_.SystemName + '\' + $_.ShareName }

# Create some variables to make the command construction simpler
PS C:> $setacl = 'C:\local\bin\SetACL.exe'
PS C:> $group = 'DOMAIN\RL-PrinterGroup'

# Iterate through the list of printers, calling SetACL for each
PS C:> foreach ( $p in $printers ) {
>> & $setacl '-on', $p , '-ot', 'prn', '-actn', 'ace','-ace',"n:$group;p:full"

It worked like a champ.

Note that I needed to run an elevated PowerShell session (run as Administrator) to be able to make the changes. Also, I tested this on a single printer before attempting to make bulk changes. I also have a nightly dump of my printer configuration. You get the idea.

You can include multiple actions for each invocation of SetACL, and there are a number of ways to collect output in case you need to follow-up on problems when applying changes to large collections of printers. Hopefully, this is enough to get you started,

Printer management with PowerShell

I find the printer management scripts included with Server 2008 bothersome. However, until we get to Server 2012, with its shiny new printer management cmdlets, I’ll keep trying to make do with what we have.

I was pleased to find an old post on the Printing Team’s blog about using the .Net System.Printing namespace with PowerShell to automate things. Some brief experimenting looks promising.

For the moment, in order to switch the printer driver version on about fifteen printers (all the same model), I used PowerShell and WMI, like this:

PS C:\> $printers = Get-WmiObject Win32_Printer -filter "Name like 'RL -%'"
PS C:\> $printers | sort Name | ft Name, DriverName -a
PS C:\> $printers | Where DriverName -eq 'HP Universal Printing PCL 6 (v5.3)' | % {
>> $_.DriverName = 'HP Universal Printing PCL 6 (v5.6.1)'
>> $_.Put()
>> }

This took quite a while to execute, but did work. What I need to do now is modify the permissions on each printQueue. I doubt I can leverage Get-ACL and Set-ACL for this.

Printer drivers and architectures with PowerShell

We have a number of 32-bit Windows 2008 print servers that we want to migrate to Windows Server 2012, for the printer management PowerShell cmdlets, among other things. I found a helpful blog post about using the PRINTBRM utility to migrate print queues, which mentions that you need to have both 32-bit and 64-bit drivers versions of all the drivers in order to migrate from a 32-bit to a 64-bit OS instance.

I wrote a little script to quickly show me which print drivers need a 64-bit version installed. It can take a moment to run if you have many printers configured.

UPDATE: I made a couple changes, most notably that the count of printers using a driver is now optional (since it can take a while on a system with lots of printers).

Lists printer drivers,  whether 32- and 64-bit versions are installed,
and how many printers are using each driver.

Includes a counter of the printers using each printer driver. On a system
with many installed printers, this can take a a little time, so this 
functionality is optional.

PS C:\local\scripts> .\Get-PrinterDriverArchitecture.ps1 | format-table -auto

Name                                       x86   x64
----                                       ---   ---
Brother HL-5250DN                         True False
Epson LQ-570+ ESC/P 2                     True False
HP Business Inkjet 2230/2280              True False
HP Business Inkjet 2250 (PCL5C)           True  True
HP Business Inkjet 2800 PCL 5             True False

This command lists the installed printer drivers, and whether 32-bit (x86)
or 64-bit (x64) drivers are available.

PS C:\local\scripts> .\Get-PrinterDriverArchitecture.ps1 | where x64 -eq $false | ft -a

Name                                      x86   x64
----                                      ---   ---
Brother HL-5250DN                        True False
Epson LQ-570+ ESC/P 2                    True False
HP Business Inkjet 2230/2280             True False
HP Business Inkjet 2800 PCL 5            True False

This command uses the Where[-Object] cmdlet to filter out those drivers that
have a 64-bit driver installed.

PS C:\local\scripts> .\Get-PrinterDriverArchitecture.ps1 -Printers | ft -a
Enumerating printers:
    WID - Kyocera TASKalfa 3050ci KX
    WC - Kyocera TASKalfa 300ci KX
    UFS - Canon iR-ADV C5035

Name                                       x86   x64 Printers
----                                       ---   --- --------
Brother HL-5250DN                         True False        5
Epson LQ-570+ ESC/P 2                     True False        0
HP Business Inkjet 2230/2280              True False        1
HP Business Inkjet 2250 (PCL5C)           True  True        0
HP Business Inkjet 2800 PCL 5             True False        1

This command includes the -Printers switch parameter to add a count of
the printers using each driver. Enumerating the printers can take a while
if there are lots of them installed, so this behavior is optional.

 - Author    : Geoffrey.Duke@uvm.edu
 - Mod. Date : May 28, 2013

param( [switch] $printers )

$wmi_drivers = get-wmiobject Win32_PrinterDriver -Property Name
$drivers = @{}
foreach ($driver in $wmi_drivers) {
    # Isolate the driver name and platform
    $name,$null,$platform = $driver.Name -split ','

    if ( -not $drivers[$name] ) {
        switch ( $platform ) {
            'Windows NT x86' { $drivers[$name] = [ordered]@{ 
                                 'Name'=$name; 'x86'=$true; 'x64'=$false }; break }
            'Windows x64'    { $drivers[$name] = [ordered]@{ 
                                 'Name'=$name; 'x86'=$false; 'x64'=$true }; break }
             default         { write-warning "Unexpect platform $platform on driver $name"}
    else {
        switch ( $platform ) {
            'Windows x64'    { $drivers[$name]['x64'] = $true; break }
            'Windows NT x86' { $drivers[$name]['x86'] = $true; break }
             default         { write-warning "Unexpect platform $platform on driver $name"}

if ( $printers ) {
    # Initialize all printer counts
    $drivers.keys | foreach { $drivers[$_]['Printers'] = 0 }
    # Add a count of the number of printers using each driver
    # With some progress info
    write-host 'Enumerating printers:'
    $count = 0
    get-wmiobject Win32_Printer -Property Name,DriverName | foreach { 
        write-host "    $($_.Name)"  -foreground darkgray

    write-host "Retrieved $count printers"

# Output collection of objects
$drivers.keys | sort | foreach { New-Object PSObject -Property $drivers[$_] } 

I hope this is useful to others.

Which Disk is that volume on?

I administer a server VM with a lot of disks, and many of them are the same size. When I need to make changes to the system’s storage, I’m always nervous that I’m going to poke the wrong disk. I could trust that the order of the disks listed in the vSphere client is the same as the order that the guest OS lists (starting at 1 and 0 respectively). But I want a little more assurance.

Using diskpart, you can list the details for individual disks, partitions and volumes, but I wanted a report showing all the disks, the partitions on those disks, and the volumes residing on those partitions. I have reported some of this info previously, using PowerShell’s Get-WMIObject cmdlet to query the Win32_DiskDrive, Win32_Partition, and Win32_Volume classes. I figured there must me a way to correlate instances of these classes.

I found these two blog posts:

They did most of the heavy lifting in building the WQL ASSOCIATOR OF queries. I put together a short script to give me a little more detail. Here’s some sample output:

PS C:\local\scripts> .\Get-DiskInfo.ps1
Disk 0 - SCSI 0:0:2:0 - 45.00 GB
    Partition 0  100.00 MB  Installable File System
    Partition 1  44.90 GB  Installable File System
        C: [NTFS] 44.90 GB ( 3.46 GB free )
Disk 5 - SCSI 0:0:2:5 - 39.99 GB
    Partition 0  40.00 GB  Installable File System
        B: [NTFS] 40.00 GB ( 34.54 GB free )

This will make it easier to be sure about the vSphere storage element that corresponds to a particular volume (or, more accurately, the Physical Disk on which the volume resides).

Here’s the actual script:

Get information about the physical disks and volumes on a system.

Get details about the physical disks and the volumes located on
those disks, to make it easier to identify corresponding vSphere
storage (VMDKs).


PS C:\> .\Get-DiskInfo.ps1

    Author: Geoff Duke <Geoffrey.Duke@uvm.edu>
    Based on http://bit.ly/XowLns and http://bit.ly/XeIqFh

Set-PSDebug -Strict

Function Main {

    $diskdrives = get-wmiobject Win32_DiskDrive | sort Index

    $colSize = @{Name='Size';Expression={Get-HRSize $_.Size}}

    foreach ( $disk in $diskdrives ) {

        $scsi_details = 'SCSI ' + $disk.SCSIBus         + ':' +
                                  $disk.SCSILogicalUnit + ':' +
                                  $disk.SCSIPort        + ':' +
        write $( 'Disk ' + $disk.Index + ' - ' + $scsi_details +
                 ' - ' + ( Get-HRSize $disk.size) )

        $part_query = 'ASSOCIATORS OF {Win32_DiskDrive.DeviceID="' +
                      $disk.DeviceID.replace('\','\\') +
                      '"} WHERE AssocClass=Win32_DiskDriveToDiskPartition'

        $partitions = @( get-wmiobject -query $part_query | 
                         sort StartingOffset )
        foreach ($partition in $partitions) {

            $vol_query = 'ASSOCIATORS OF {Win32_DiskPartition.DeviceID="' +
                         $partition.DeviceID +
                         '"} WHERE AssocClass=Win32_LogicalDiskToPartition'
            $volumes   = @(get-wmiobject -query $vol_query)

            write $( '    Partition ' + $partition.Index + '  ' +
                     ( Get-HRSize $partition.Size) + '  ' +

            foreach ( $volume in $volumes) {
                write $( '        ' + $volume.name + 
                         ' [' + $volume.FileSystem + '] ' + 
                         ( Get-HRSize $volume.Size ) + ' ( ' +
                         ( Get-HRSize $volume.FreeSpace ) + ' free )'

            } # end foreach vol

        } # end foreach part

        write ''

    } # end foreach disk


function Get-HRSize {
        [Parameter(Mandatory=$True, ValueFromPipeline=$True)]
        [INT64] $bytes
    process {
        if     ( $bytes -gt 1pb ) { "{0:N2} PB" -f ($bytes / 1pb) }
        elseif ( $bytes -gt 1tb ) { "{0:N2} TB" -f ($bytes / 1tb) }
        elseif ( $bytes -gt 1gb ) { "{0:N2} GB" -f ($bytes / 1gb) }
        elseif ( $bytes -gt 1mb ) { "{0:N2} MB" -f ($bytes / 1mb) }
        elseif ( $bytes -gt 1kb ) { "{0:N2} KB" -f ($bytes / 1kb) }
        else   { "{0:N} Bytes" -f $bytes }
} # End Function:Get-HRSize


Please let me know if you find this helpful.

String arrays and mandatory parameters

I have been working on a function to convert the output of NET SHARE <sharename> commands into usable PowerShell objects. In the course of my work, I was storing the output of the command in a variable, which I later pass into a parsing function. Curiously, the function I developed iteratively in the console worked fine, but when I dressed it up in my script, it failed:

test-array : Cannot bind argument to parameter 'foo' because it is an empty string.
At line:1 char:12
+ test-array $party
+            ~~~~~~
    + CategoryInfo          : InvalidData: (:) [test-array], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorEmptyStringNotAllowed,test-array

I had verified that the variable was of type System.Array, and that it had string elements. After banging my head on it for a while, I decided to break out the parameter handling and test it separately. I wrote a quick function to accept and process the elements of a string array:

function test-array {
param( [string[]] $foo )
    $i = 0
    foreach ( $line in $foo ) {
        write "[$i] $line"

Continue reading

Listing parent of AD object in PowerShell

Recently, I wanted to provide a client with a list of groups that related to some work he was doing. I wanted the group names as well as their location with AD. Although I often use the ds* commands or excellent ADfind tool for this type of task, I had been working in PowerShell on another project, so I decided to use the PowerShell ActiveDirectory module.

The Get-ADGroup Cmdlet pulled out the groups easily enough, but the there wasn’t a property representing the group object’s parent, nor is there an LDAP property that I could request (AFAIK). The object’s parent is contained within the DistinguishedName (DN) property, though.

For a group with the following DN:

CN=FOO-FileServices Administrators,OU=FOO,OU=Departments,DC=uvm,...

I just need to strip off the CN. I could split the DN on commas, remove the first element, and then reassemble what’s left to get the parent. I also needed to avoid splitting on an LDAP-escaped comma where a value actually contains a comma (e.g., CN=).

PS> $dn -split '(?<![\\]),'

Continue reading

Script: Shadow Copy Report

We use EMC NetWorker for our enterprise backup solution. Since we migrated our primary file server from a NetApp filer to a native Windows server, we’ve been having a recurring problem with all the Shadow Copies for a volume getting deleted. There are strong indications that the problem is related to the NetWorker backups.

As we have been working on this issue with EMC (since the first week in January!), I wrote a script to tell me two things each morning; how many snapshots exist for each volume, and what VSS errors were logged, if any.

I thought someone might find it useful, so I’ve posted it as a separate page (the script doesn’t fit nicely in the column on the blog).

PowerShell Script: chksnap.ps1

Custom FSRM notification script

I’ve been working on a script to generate an informative message to users when they exceed quota thresholds on our file server. The features of the File Server Resource Manager (FSRM) provides a variety of useful variables that can be plugged into an automated email. However, we have found that it’s often very useful to provide more information about the kind of files that a user is storing, something akin to the output of the very useful and free utility WinDirStat.

I’ve made progress on the script that generates the email. However, I’ve run into a snarl in trying to configure the quota notification to run the script. The script runs just fine from a command prompt, even from a command prompt running as the Local System account. But when I trigger an FSRM event that should drive the script, I get an error in the Application Log:


Continue reading

PowerShell – find a free IP

Since we don’t use DHCP in our server subnets, I frequently have to locate free IP addresses when deploying a server. I remembered reading a TechNet Magazine article by Don Jones that used the PowerShell PROCESS block and the Win32_PingStatus WMI class in a sample script.

I took that and rewrote the function a little:

function Ping-Address {
    $ping = 'unreachable'
    $formatstring = "{0,-15}  {1,-12} {2}"
    $queryString  = "SELECT * FROM Win32_PingStatus"
    $queryString += " WHERE Address = '$_' AND"
    $queryString += " ResolveAddressNames = $true"
    $results = Get-WmiObject -query $queryString
    foreach ($result in $results) {
      if ($results.StatusCode -eq 0) {
        $ping = 'ping!'
    $formatstring -f $_,$ping,$results.ProtocolAddressResolved

I can then use this function like so:

PS Z:\> (14..20) | %{ '132.198.59.'+ $_.ToString()} | Ping-Address    ping!    ping!    ping!        xxxxxxx.campus.ad.uvm.edu    ping!        xxxxxxx.uvm.edu    unreachable    ping!        xxxx.uvm.edu    unreachable

I’ve already used it a bunch of times. I think I will probably grow this into a real script, taking the IP address range info as parameters. Another day…

Monday – 2009-09-28

Today’s issues:

  • Backup issues
  • Shared folder quotas
  • Printer configurations
  • Data execution protection

I created a Server 2008 x64 guest for managing 64-bit drivers on our shared printers. It works much better than trying to use Printer Management MMC in RSAT on Windows 7.

One hiccup I ran into while install the Ricoh PCL6 Driver for Universal Print was that it was missing a file. Fortunately, I had also download and extracted the non-universal PCL6 drivers and the file was present in the drivers for the corresponding platforms (x86, x64).

Looking at adding a –WhatIf switch parameter to my SharePoint Backup powershell script. Useful info at Negating PowerShell switch parameters.

Now wrestling with Task Scheduler and PowerShell invocation syntax.