Powershell Convert REG_BINARY to Security Discriptor String Format?

Hi all,
I have a requirement to read this key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\lanmanserver\DefaultSecurity\SrvsvcSharePrintInfo. The value of this key is in REG_BINARY format. I need to convert this into a string or a Security Discriptor String Format.

Anybody know how to do this?
WTarltonAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

Chris DentPowerShell DeveloperCommented:

That's shared printer access rights? We can convert it if we can find sufficient documentation for it's format.

It does appear to work with the ConvertStringSecurityDescriptorToSecurityDescriptor function... wonder if we can make PS use that. Needs a spot of testing, I'll be right back.

Chris
0
Chris DentPowerShell DeveloperCommented:

This gets us most of the way. Win32_SecurityDescriptorHelper is lovely, but you'll need a rather recent operating system to be able to use it (I'm on Windows 7, I can't test on anything else right now).

Now we just need a better enumeration for the printer access rights. It'll have the same numeric values as the FileSystemRights enumeration used below, it's the names that don't match up.

Chris
Function ConvertTo-SecuritySummary ( [Byte[]]$ByteArray )
{
  $SD = ([WMIClass]"Win32_SecurityDescriptorHelper").BinarySDToWin32SD($Value).Descriptor

  Write-Host "Owner: $($SD.Owner.Domain)\$($SD.Owner.Name)"
  Write-Host "Group: $($SD.Group.Domain)\$($SD.Group.Name)"

  $ControlFlags = [Enum]::GetNames([Security.AccessControl.ControlFlags]) | ?{ $_ -ne "None" } | %{
    If (($SD.ControlFlags -BAnd [Int64][Security.AccessControl.ControlFlags]::$_) -eq [Int64][Security.AccessControl.ControlFlags]::$_) {
      $_
    }
  }
  If ($ControlFlags -eq $Null) { $ControlFlags = "None" }

  Write-Host "ControlFlags: $ControlFlags"

  $SD.DACL | %{

    $Trustee = $_.Trustee.Name
    If ($_.Trustee.Domain -ne $Null) {
      $Trustee = "$($_.Trustee.Domain)\$Trustee"
    } Else {
      $Trustee = "$($_.Trustee.SIDString)"
    }

    New-Object Security.AccessControl.FileSystemAccessRule( `
      $Trustee, $_.AccessMask, $_.AceType)
  }
}

ConvertTo-SecuritySummary (Get-Item "HKLM:\SYSTEM\CurrentControlSet\Services\lanmanserver\DefaultSecurity").GetValue("SrvsvcSharePrintInfo")

Open in new window

0
Chris DentPowerShell DeveloperCommented:
Bit of an error above. Fixed in this version.

If I can't find an enumeration for printers and you need the rights to reflect accurately I'll make one next week.

Chris
Function ConvertTo-SecuritySummary ( [Byte[]]$ByteArray )
{
  $SD = ([WMIClass]"Win32_SecurityDescriptorHelper").BinarySDToWin32SD($ByteArray).Descriptor

  Write-Host "Owner: $($SD.Owner.Domain)\$($SD.Owner.Name)"
  Write-Host "Group: $($SD.Group.Domain)\$($SD.Group.Name)"

  $ControlFlags = [Enum]::GetNames([Security.AccessControl.ControlFlags]) | ?{ $_ -ne "None" } | %{
    If (($SD.ControlFlags -BAnd [Security.AccessControl.ControlFlags]::$_) -eq [Security.AccessControl.ControlFlags]::$_) {
      $_
    }
  }
  If ($ControlFlags -eq $Null) { $ControlFlags = "None" }

  Write-Host "ControlFlags: $ControlFlags"

  $SD.DACL | %{

    $Trustee = $_.Trustee.Name
    If ($_.Trustee.Domain -ne $Null) {
      $Trustee = "$($_.Trustee.Domain)\$Trustee"
    } Else {
      $Trustee = "$($_.Trustee.SIDString)"
    }

    New-Object Security.AccessControl.FileSystemAccessRule( `
      $Trustee, $_.AccessMask, $_.AceType)
  }
}

ConvertTo-SecuritySummary (Get-Item "HKLM:\SYSTEM\CurrentControlSet\Services\lanmanserver\DefaultSecurity").GetValue("SrvsvcSharePrintInfo")

Open in new window

0
Simplify Active Directory Administration

Administration of Active Directory does not have to be hard.  Too often what should be a simple task is made more difficult than it needs to be.The solution?  Hyena from SystemTools Software.  With ease-of-use as well as powerful importing and bulk updating capabilities.

WTarltonAuthor Commented:
Unfortunatly it's not working I get this as output ...

HP LaserJet 4015
Cannot convert value "Win32_SecurityDescriptorHelper" to type "System.Management.ManagementClass". Error: "Not found "
At X:\SetPrinterPermissions.ps1:26 char:20
+   $SD = ([WMIClass] <<<< "Win32_SecurityDescriptorHelper").BinarySDToWin32SD($ByteArray).Descriptor
    + CategoryInfo          : NotSpecified: (:) [], RuntimeException
    + FullyQualifiedErrorId : RuntimeException

Owner: \
Group: \
ControlFlags: None
0
Chris DentPowerShell DeveloperCommented:

Which version of Windows are you running? Win32_SecurityDescriptorHelper is very new...

Chris
0
WTarltonAuthor Commented:
Unfortunatly XP :(

Do you think there is a way I could use the Get-ACL command to get list? Problem is I am enumerating all the printers on a clustered file server. The only information I have to work off of is the Security value for each printer which is a REG_BINARY. For security reasons my employer wants me to remove Power users and creater owner from the securiy of all the printers as a regular scheduled task.
0
WTarltonAuthor Commented:
Even a function to convert binary to string equivelent would be helpful I would think.
0
Chris DentPowerShell DeveloperCommented:

> Do you think there is a way I could use the Get-ACL command to get list?

Short answer to that is no.

Are you taking the Default ACL value from the egirstry or was that just an example?

If not, how are you getting the value you're looking at?

> Even a function to convert binary to string equivelent would be helpful I would think.

For the ACL? The structure is rather more complex than that.

There are a number of ways to convert from binary / byte to string. Do you know the text encoding you're dealing with? ASCII / Unicode?

For Unicode:

$Unicode = New-Object Text.UnicodeEncoding
$Unicode.GetString($ByteArrayValue)

For ASCII:

([System.Text.Encoding]::ASCII).GetString($ByteArrayValue)

It depends on what you have as input. Sometimes it's not even as complex as that.

Chris
0
WTarltonAuthor Commented:
So I guess my solution on XP is just to setup a printer the way I want it and just copy the binary value to the other printers...?
0
Chris DentPowerShell DeveloperCommented:

The value in the registry is the default ACL value. I believe it gets copied to new printers when you set them up, it doesn't represent the current ACL.

That is unless you're getting the current ACL from elsewhere.

I can write you a decoder for the binary ACL if you need it, but I do need to know how you're getting to it. There are so many interfaces, it would be a waste of half a day to write a decoder for the wrong one.

Chris
0
WTarltonAuthor Commented:
I am pulling the printers out of the registry since it is a cluster...

Clear-Host

$Spoolers = @()
$Resources = Get-ChildItem HKLM:\Cluster\Resources
foreach ($Resource in $Resources)
{
      $values = Get-ItemProperty $Resource.pspath
      
      foreach ($value in $values)
      {
            if ($value.Type -ne $Null -and $value.Type.tolower() -eq "print spooler")
            {
                  [string]$Path = $Resource.pspath + "\Parameters\Printers"
                  $Spoolers += $Path
          }
      }
}

foreach ($Spooler in $Spoolers)
{
      $Printers = Get-ChildItem $Spooler
      foreach ($Printer in $Printers)
      {
            #$Printer | fl *
            Write-Host -ForegroundColor cyan $Printer.PSChildName
            $values = Get-ItemProperty $Printer.pspath
            
            Write-Host $Values.security #<--- this is the security discriptor in binary form
            }
}
0
Chris DentPowerShell DeveloperCommented:

Excellent, thank you. I won't be able to build this until tomorrow, but I'll sort it out.

Chris
0
WTarltonAuthor Commented:
No thank you for helping!
0
Chris DentPowerShell DeveloperCommented:

I'm found another .NET class that may get around the need for Win32_SecurityDescriptorHelper. It'll be nice if it works, but it's no bother if it doesn't, I'd written half of a script to read the binary descriptor before finding this one.

It's structured in something like the same way as the return from Get-Acl, although it's a lot less friendly.

Still, if this returns properly we can wrap things around it to make it more friendly. It's plugged into your code below, this is the output we're expecting to see:

ControlFlags           : DiscretionaryAclPresent, SelfRelative
Owner                  : S-1-5-18
Group                  : S-1-5-18
SystemAcl              :
DiscretionaryAcl       : {System.Security.AccessControl.CommonAce, System.Security.AccessControl.CommonAce, System.Secu
                         rity.AccessControl.CommonAce, System.Security.AccessControl.CommonAce...}
ResourceManagerControl : 0
BinaryLength           : 228

The Owner, Group and DisretionaryAcl entries can be made easier on the eyes. But getting the output above would be a huge step. If it works, how would you like to see the output from this?

If it doesn't work I'll finish off the script.

Chris
Clear-Host

$Spoolers = @()
$Resources = Get-ChildItem HKLM:\Cluster\Resources
foreach ($Resource in $Resources)
{
      $values = Get-ItemProperty $Resource.pspath
      
      foreach ($value in $values)
      {
            if ($value.Type -ne $Null -and $value.Type.tolower() -eq "print spooler")
            {
                  [string]$Path = $Resource.pspath + "\Parameters\Printers"
                  $Spoolers += $Path
          }
      }
}

foreach ($Spooler in $Spoolers)
{
      $Printers = Get-ChildItem $Spooler
      foreach ($Printer in $Printers)
      {
            #$Printer | fl *
            Write-Host -ForegroundColor cyan $Printer.PSChildName
            $values = Get-ItemProperty $Printer.pspath
            
            # Convert the binary SD to a RawSecurityDescriptor and display it
            New-Object Security.AccessControl.RawSecurityDescriptor($($Values.security), 0)
      }
}

Open in new window

0
WTarltonAuthor Commented:
Sweet thanks!! I will be able to add and remove users too with this?
0
Chris DentPowerShell DeveloperCommented:

Yes, we can.

It needs a bit of care this, first lets deal with removing existing entries. This is the easy one :)

I'm using a folder called Test as my example here, we find the binary form of that security descriptor using Get-Acl. That gives us something to play with.

Chris
$OldBinarySD = (Get-Acl "Test").GetSecurityDescriptorBinaryForm()

# Convert the Binary SD into a Security Descriptor
$SD = New-Object Security.AccessControl.RawSecurityDescriptor($OldBinarySD, 0)

# Removing Access Control Entries for a specific user
For ($i = 0; $i -lt $SD.DiscretionaryAcl.Count; $i++)
{
  # Translate the SID into an NTAccount
  $NTAccount = $SD.DiscretionaryAcl[$i].SecurityIdentifier.Translate([Security.Principal.NTAccount]).Value
  # Test the account name
  If ($NTAccount -Like "*\SomeUser")
  {
    Write-Host "Removing $NTAccount at index $i" -ForegroundColor Red
    # Remove the ACE from the ACL
    $SD.DiscretionaryAcl.RemoveAce($i)
  }
}

# Return to the Binary form
$NewBinarySD = New-Object Byte[] $SD.BinaryLength
$SD.GetBinaryForm($NewBinarySD, 0)
# This contains the binary form which needs writing back to the registry key
$NewBinarySD

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
Chris DentPowerShell DeveloperCommented:

And adding.

The hardest part here will be deciding on the value for $AccessMask, the value I've set below is entirely arbitrary.

Perhaps the simplest way to figure out that value is to compare it with something that works for you on the current ACL. It is possible to build it up from individual entries, trouble is you need to know what those are first.

Chris
# Get the NT Account
$NTAccount = New-Object Security.Principal.NTAccount("domain", "user")
# Convert the NT Account into a SID
$SID = $NTAccount.Translate([Security.Principal.SecurityIdentifier])

# Assign a value for AccessMask (access rights)
$AccessMask = 1179817

# Create the new Access Control Entry
$CommonAce = New-Object Security.AccessControl.CommonAce(
  @("ObjectInherit", "ContainerInherit"),
  [Security.AccessControl.AceQualifier]::AccessAllowed,
  $AccessMask,
  $SID,
  $False,
  $Null)

# Add the ACE to the end of the current ACL. Order must be considered if the
# ACL is extensive.
$SD.DiscretionaryAcl.InsertAce($SD.DiscretionaryAcl.Count, $CommonAce)

# Return to the Binary form
$NewBinarySD = New-Object Byte[] $SD.BinaryLength
$SD.GetBinaryForm($NewBinarySD, 0)

# This contains  the binary form which needs writing back to the registry key
$NewBinarySD

Open in new window

0
WTarltonAuthor Commented:
Testing now will be back with you shortly..
0
Chris DentPowerShell DeveloperCommented:

I'll cross my fingers ;)

Chris
0
WTarltonAuthor Commented:
Ok im testing it out on a printer. I ran it and it did remove the power users group from the script side but when I actually go into the printer and lookup the security it is still there. Im thinking either that is not really where the security is for that printer or there are multiple locations i need to change the SD. But it did work! Awesome job! Still need to test adding a user.

Looking into it...
0
Chris DentPowerShell DeveloperCommented:

Hmm it may only read the descriptor from the registry when it starts the device. I can't imagine restarting would be particularly easy :)

Chris
0
WTarltonAuthor Commented:
It's very easy ...just kick everybody off and reboot the server by accident and deny you know what happned lol...

brb :)
0
Chris DentPowerShell DeveloperCommented:

Mmm yes that would do it :)

Chris
0
WTarltonAuthor Commented:
That didn't seem to work. However your script does work so i will give you the credit. Thank you for all your help A++.
0
WTarltonAuthor Commented:
Awesome effort thanks for all the help much appreciated!
0
WTarltonAuthor Commented:
Got it working! Had to actually stop the spooler and start it. The restart service feature wont do it.
0
Chris DentPowerShell DeveloperCommented:

That's great news, I'm glad it finally got there :)

Chris
0
WTarltonAuthor Commented:
Hey Chris you still around? All is working great but I have a question.

The AccessMask when adding a user is that what controls the rights to Print, Manage Printers, Manage Documents, etc. ?

0
Chris DentPowerShell DeveloperCommented:

Yes, that's right.

It's a bit-field, that is, it contains a number of flags which can be set to on or off. Each of those corresponds to a right on the object. Things like Full Control, etc are composite rights, a mixture of several separate flags.

I don't know the values for Printers, they're not as well documented as File System Rights although there is a lot of similarity. They are documented "somewhere", just a case of finding them.

The quickest way to choose a value is to set up an ACL you can easily read, then apply the same numeric value for the access mask.

If that fails they can be reverse engineered from existing rights if you're willing to go through each of the tick-boxes and echo out the AccessMask each time.

Chris
0
WTarltonAuthor Commented:
Cool thanks!
0
WTarltonAuthor Commented:
Is there a way to pull the current access mask using the code you supplied me?
0
Chris DentPowerShell DeveloperCommented:

Sure, we almost have it above :) A quick modification gives this, is that sufficient for your needs?

Chris
$OldBinarySD = (Get-Acl "Test").GetSecurityDescriptorBinaryForm()

# Convert the Binary SD into a Security Descriptor
$SD = New-Object Security.AccessControl.RawSecurityDescriptor($OldBinarySD, 0)

# Removing Access Control Entries for a specific user
For ($i = 0; $i -lt $SD.DiscretionaryAcl.Count; $i++)
{
  # Translate the SID into an NTAccount
  $NTAccount = $SD.DiscretionaryAcl[$i].SecurityIdentifier.Translate([Security.Principal.NTAccount]).Value

  # Get the current ACE and add the NTAccount object as a property. Leave it in the output pipe
  $SD.DiscretionaryAcl[$i] | Select-Object *, 
    @{n='NTAccount';e={ $NTAccount }}
}

Open in new window

0
WTarltonAuthor Commented:
Your the man! Works brilliantly.
0
WTarltonAuthor Commented:
Hey Chris im running into a problem and wondering if maybe you might know. When I add a user using your access flag of 1179817 it works. When I setup myself as a user manually it gives me a mask of 131080. If I add a user with the mask of 131080 it just does not give me any rights,...it even says I have "special permissions"....do you have any idea why this may be?

Here is what comes back when I add myself by hand and then pull my access mask...when I add it through code it looks identical when i pull it but just does not want to work.

Domain\Username@{BinaryLength=36
#  AceQualifier=AccessAllowed
#  IsCallback=False
#  OpaqueLength=0
#  AccessMask=131080
#  SecurityIdentifier=S-1-5-21-3180253472-107517923-3739974048-156707
#  AceType=AccessAllowed
#  AceFlags=None
#  IsInherited=False
#  InheritanceFlags=None
#  PropagationFlags=None
#  AuditFlags=None
#  NTAccount=Domain\Username}
0
Chris DentPowerShell DeveloperCommented:

The main permissions page displays certain, very specific, combinations of access rights and flags. "Special permissions" is less picky, it'll show you everything.

If the access mask value is correct I would compare AceFlags, InheritanceFlags and PropagationFlags. Any of those can cause the rights to appear under the special permissions. If you make all of them match it should appear in the same place in the GUI when set with the script.

Chris
0
WTarltonAuthor Commented:
Ok I fixed that by turning @("ObjectInherit", "ContainerInherit") to "None"
For some reason though when I still add it by hand it works fine and gives me a mask of 131080. When I set it with code to the same value it does not give me the same rights. Any clues on that one?
0
Chris DentPowerShell DeveloperCommented:

hmm if you add the rights in the GUI for one use, then attempt to set the same in a script for another, does it show exactly the same thing if the script to display them is run?

If that made any sense...

If the access mask is the same then the rights are. The GUI is far more picky about what and how it displays things, we need to find the difference to appease it.

Chris
0
WTarltonAuthor Commented:
Yes if I set my user by hand and then run the script to get the access mask it returns this

Domain\Username@{BinaryLength=36
#  AceQualifier=AccessAllowed
#  IsCallback=False
#  OpaqueLength=0
#  AccessMask=131080
#  SecurityIdentifier=S-1-5-21-3180253472-107517923-3739974048-156707
#  AceType=AccessAllowed
#  AceFlags=None
#  IsInherited=False
#  InheritanceFlags=None
#  PropagationFlags=None
#  AuditFlags=None
#  NTAccount=Domain\Username}

If I set the access mask in code to 131080 I get the same returned when pulling the access mask this :

Domain\Username@{BinaryLength=36
#  AceQualifier=AccessAllowed
#  IsCallback=False
#  OpaqueLength=0
#  AccessMask=131080
#  SecurityIdentifier=S-1-5-21-3180253472-107517923-3739974048-156707
#  AceType=AccessAllowed
#  AceFlags=None
#  IsInherited=False
#  InheritanceFlags=None
#  PropagationFlags=None
#  AuditFlags=None
#  NTAccount=Domain\Username}

yet it does not work even though everything is identical.
0
Chris DentPowerShell DeveloperCommented:

It's really not very helpful...

Does the same situation repeat if you load the ACE in (so it appears with Special Permissions) then report on it with the script?

Clutching at straws :)

Chris
0
WTarltonAuthor Commented:
Yea nothing seems to work. I've been looking for the Access Masks online buy have yet to find them. I think my only hope it to find the actual individual flags and what they stand for and to add them up.
0
WTarltonAuthor Commented:
If you add a printer and give yourself Print and Manage documents rights can you tell me what mask you get?
0
Chris DentPowerShell DeveloperCommented:

Good question, I have to run off home but I'll try and test that from there.

Chris
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Powershell

From novice to tech pro — start learning today.