Bulk change print permissions with powershell

This topic contains 4 replies, has 3 voices, and was last updated by  Rohn Edwards 7 months, 3 weeks ago.

  • Author
    Posts
  • #62442

    TheDL
    Participant

    I have a 2012r2 print server and I just need to add a print permission to all existing queues, without overwriting the existing permissions.

      I found a handfull of suggestions on various online forums, but have yet to get this done.

      I tried:

      $security = get-printer “printer with changes” -full
      get-printer * | Foreach-Object {set-printer $_.name -PermissionSDDL $security.PermissionSDDL}

      and got a lot of errors, sounded like the spooler service was crashing (it was up when I checked), here's the error:

      set-printer : An error occurred while performing the specified operation. See the error details for more information.
      At line:1 char:31
      + get-printer *|Foreach-Object {set-printer $_.name -PermissionSDDL $security.Perm …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (MSFT_Printer (N…= “”, Type = 0):ROOT/StandardCimv2/MSFT_Printer) [Set-P
      rinter], CimException
      + FullyQualifiedErrorId : HRESULT 0x800706be,Set-Printer

      set-printer : The spooler service is not reachable. Ensure the spooler service is running.
      At line:1 char:31
      + get-printer *|Foreach-Object {set-printer $_.name -PermissionSDDL $security.Perm …
      + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      + CategoryInfo : NotSpecified: (MSFT_Printer:ROOT/StandardCimv2/MSFT_Printer) [Set-Printer], CimException
      + FullyQualifiedErrorId : HRESULT 0x800706ba,Set-Printer

      I then tried a suggestion to prevent this function from crashing the spooler service when there is a large number of queues:

      I wrapped the script into a PS-Job so I could leverage a Start-Sleep and wait for each queue to process.

      $security = get-printer insertprinterqueuehere -computer insertprintserverhere -full
      $printServerTarget = "inserttargetprinterserverhere"
      $printerQueueList = get-printer * -computer $printServerTarget

      Foreach ($printerQueue in $printerQueueList)
      {
      set-printer $printerQueue.Name -computer $printServerTarget -PermissionSDDL $security.PermissionSDDL -AsJob

      While (Get-Job -IncludeChildJob -State "Running")
      {
      Start-sleep 5
      }
      Get-Job | Remove-Job
      }

      It ran for an exceptionally long time, and I didn't see any permissions being modified while it ran, however when it was done I discovered that it over-wrote the unique permissions on 1000+ queues. ARGH!

      I then tried this:

      (Get-Printer 'TEMP' -Full).PermissionSDDL | Out-File 'C:\permissions.txt'
      Then I use something like this to pull my saved Security settings into a variable. $perms = Get-Content 'C:\permissions.txt'

      Then to push it to particular printer you can do the following

      Set-Printer 'NEWPRINTER' -PermissionSDDL $perms

      This actually worked also, however it over-wrote the permissions on the destination printer. This isn't going to work, I have 1000+ queues with non-uniform permissions and I just need to add a group to every queue and specify a certain permission.

      Can anyone help?

  • #62743

    Don Jones
    Keymaster

    Given the way these work, you're probably going to have to query the permissions and then overwrite them, adding whatever you want at the want at that time. That's what the GUI actually does.

  • #62787

    Rohn Edwards
    Participant

    Don's right. You want to ask each printer what its current security descriptor (SD) contains, copy that, add your new permission(s), and finally overwrite the whole thing with the new copy.

    There are some modules out there that can help with this, but if you're just looking to add a specific permission, you can do this out of the box with some .NET classes and a few lines of PowerShell. It might look a little crazy, but it's not that hard to follow what's happening if you read over the steps a few times.

    First, you'll need to get the SD, which you're already doing with Get-Printer. You'd have to be crazy to try to work directly with SDDL it's returning, though. Thankfully, the .NET Framework does an awesome job working with security descriptors (really). There's no built-in class (at least that I know of) that deals with printer security descriptors specifically, but there is a generic class that will let you read and modify security descriptors if you have the SDDL or binary form: System.Security.AccessControl.CommonSecurityDescriptor.

    $SddlString = Get-Printer -Full | select -First 1 -ExpandProperty PermissionSDDL
    $SecurityDescriptor = New-Object System.Security.AccessControl.CommonSecurityDescriptor (
        $true, # Printers are containers (their ACEs can have inheritance and propagation flags)
        $false, # Not an AD security descriptor
        $SddlString
    )
    

    At that point, you'd be interested in the DiscretionaryACL property. If you look at it directly, it'll make your head hurt, so you'll want to pretty it up some (this part's optional if you know what you're adding, by the way, so you can just over it if you'd like):

    $SecurityDescriptor.DiscretionaryAcl |
        select @{N='Principal'; E={ $_.SecurityIdentifier.Translate([System.Security.Principal.NTAccount]) }}, AccessMask, InheritanceFlags, PropagationFlags
    

    That's still not nearly as nice as the Windows GUI, but you can start to get an idea of what each ACE actually looks like in the security descriptor. You could further pretty this up by translating the access mask into friendly strings, and by translating the inheritance and propagation flags into a single 'Applies To' string, but that's up to you.

    To make changes to the SD, you'll first you need to know what permission(s) you want to add (or remove if you were trying to do that). I'd do that by capturing the SDDL on a printer without the permission you want, then adding the permission in the GUI, then capturing the SDDL again. If you saved those strings into $SddlBefore and $SddlAfter variables, you could do something like this:

    # Make sure you capture $BeforeSddl and $AfterSddl
    
    $GetDacl = {
        $SecurityDescriptor = New-Object System.Security.AccessControl.CommonSecurityDescriptor (
           $true, # Printers are containers (their ACEs can have inheritance and propagation flags)
           $false, # Not an AD security descriptor
           $args[0]
        )
    
        $SecurityDescriptor.DiscretionaryAcl
    }
    
    $AcesToAdd = Compare-Object (& $GetDacl $BeforeSddl) (& $GetDacl $AfterSddl) -Property AccessMask, AceType, SecurityIdentifier, AceFlags -PassThru | where SideIndicator -eq '=>'
    

    Then $AcesToAdd would have the difference(s) between your before and after snapshot of the printer permissions. The important parts you'd want to save from each $AcesToAdd ACE are the AceType, SecurityIdentifier, AccessMask, InheritanceFlags, and PropagationFlags. If you know those, then you can use the AddAccess() method for the CommonSecurityDescriptor object, and then get the SDDL form to assign it back to the printer/print server. If you put it all together, you would have something like this:

    # You can assign something to $Printer however you'd like, e.g., you could make all of this
    # a ForEach-Object scriptblock and assign $Printer = $_
    $Printer = Get-Printer -Full | select -First 1
    
    $SddlString = $Printer.PermissionSDDL
    $SecurityDescriptor = New-Object System.Security.AccessControl.CommonSecurityDescriptor (
        $true, # Printers are containers (their ACEs can have inheritance and propagation flags)
        $false, # Not an AD security descriptor
        $SddlString
    )
    
    # Give 'Users' the 'Manage Documents' permission:
    $SecurityDescriptor.DiscretionaryAcl.AddAccess(
        'Allow',                           # AccessControlType
        ([System.Security.Principal.NTAccount] 'Users').Translate([System.Security.Principal.SecurityIdentifier]),
        268435456,                         # AccessMask
        'ContainerInherit, ObjectInherit', # InheritanceFlags
        'InheritOnly'                      # PropagationFlags
    )
    
    # Overwrite the old SD with the modified version:
    $Printer | Set-Printer -PermissionSDDL $SecurityDescriptor.GetSddlForm('All') -Confirm
    
  • #63087

    TheDL
    Participant

    Thanks for the replies guys! Kind of what I expected based on my experience, although it's really too bad the functionality to add to a print queue permission without over-writing it doesn't exist. I can (sort of) follow your method Rohn, but I think it would be cumbersome to query the unique permission for each queue, then re-apply it with the additional new permission I'm trying to add. I actually found a very easy way to achieve the intended effect without using print permissions at all; we were just trying to add a 'deny print' group to all print queues, to prevent a user from printing (and others should this situation arise again). Turns out our print-auditing software, Papercut has a very simple method to do this. Problem solved. I will consider the method Rohn laid out should we need to do something like this again in the future. Perhaps a dumb question (I'm a powershell beginner), what's with the red ellipsis' sections in your example?

    • #63093

      Rohn Edwards
      Participant

      Ignore the red ellipsis'. Looks like I somehow screwed the formatting of the spaces up when I pasted the code. Those are 'non-breaking spaces' instead of plain spaces.

You must be logged in to reply to this topic.