Categories

Monthly Archives: May 2014

File system ACLs–function to add ACL

I thought that today I’d start putting together a function to add an ACL to a file system object. The starting point is the code that stepped through the process in an earlier post:

http://msmvps.com/blogs/richardsiddaway/archive/2014/05/26/file-system-acls-creating-an-acl.aspx

function add-acl {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[ValidateScript({Test-Path -Path $_ })]
[string]$path,

[Parameter(Mandatory=$true)]
[string]$trusteeName,

[Parameter(Mandatory=$true)]
[ValidateSet("Read", "Write", "ListDirectory", "ReadandExecute", "Modify", "FullControl")]
[string]$permission = "Read",

[switch]$deny

)

$fsr = [System.Security.AccessControl.FileSystemRights]::$permission


if ($deny) {
  $alwdny = [System.Security.AccessControl.AccessControlType]::Deny
}
else {
  $alwdny = [System.Security.AccessControl.AccessControlType]::Allow
}


$acr = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $trusteeName, $fsr, $alwdny

$acl = Get-Acl -Path $path
$acl.AddAccessRule($acr)


Set-Acl -Path $path -AclObject $acl -Passthru
}

The parameters supply the path to the object, the trustee receiving the permissions, the permission and if its being denied.

The function creates the appropriate objects for the file system rights and access control type and then creates an access rule.

Get-Acl is used to fetch the current acl to which the new access rule is added. Set-Acl is used to overwrite the ACL.

One thing that hasn’t been covered is the Inheritance flags – they will be added in the next iteration of the function.

PowerShell Scripting Best Practices

Ed Wilson is running a series on Best Practices on his Hey, Scripting Guy blog at the moment.  I especially like today’s which is on simple scripts.  I do a lot of quick and dirty scripts that end up being thrown away at the  end of the project or incorporated into more formal modules for reuse.

Simple scripts are a very powerful admin tool and one that a lot of people don’t think about as they are conditioned into the idea that everything should be a module and other big code concepts. Don’t get me wrong – modules and everything else around enterprise level scripting are very important and a lot of my work is done that way but the quick, utility style script still has its place.

Check out Ed’s article at

  http://blogs.technet.com/b/heyscriptingguy/archive/2014/05/28/powershell-best-practices-simple-scripting.aspx

File system ACLs – copying ACLs

A comment was left on the first post in the series asking if I could show how to copy ACLs from one object to another.  For the sake of this post we’ll assume that the ACLs from c:\test will be copied to c:\test2.

If this is one shot deal you can just use the PowerShell pipeline:

Get-Acl -Path C:\Test | Set-Acl -Path C:\Test2

If you need something that will be used more frequently – how about a copy-acl function:

function copy-acl {
[CmdletBinding()]
param (
[ValidateScript({Test-Path -Path $_ })]
[string]$path,

[ValidateScript({Test-Path -Path $_ })]
[string]$destination
)

  try
  {
     Get-Acl -Path $path | Set-Acl -Path $destination -Passthru -ErrorAction Stop
  }
  catch
  {
     Throw "Error setting ACL on $destination"
  }
}

Get the source and target paths as parameters – validation scripts are used to determine if the paths exist.

Use the Get—Acl  | Set-Acl combination from earlier to set the ACL on the destination.  wrap it in a try/catch and use –Passthru on Set-Acl to see some output

File System ACLs – creating an ACL

Last time you saw that the permissions assign to a file system object are built from instances of the System.Security.AccessControl.FileSystemAccessRule class.  Run

Get-Acl -Path c:\test | fl *

and look at the Access property.

Drilling into an individual ACL they look like this:

FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

You see the documentation for the class at http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.filesystemaccessrule(v=vs.110).aspx

Creating a new access rule starts by creating a new instance of the class. The documentation shows 4 constructors – ways to build an instance of the class. The simplest requires the name of a user account (or group), the type of operation associated with the rule and whether the operation is allowed or denied.

First off you need to define some data to use during the creation process:

You need to define the user

$user = "$($env:COMPUTERNAME)\Newuser"

The type of access they have

$fsr = [System.Security.AccessControl]::FullControl

See http://msdn.microsoft.com/en-us/library/system.security.accesscontrol(v=vs.110).aspx for the full list

And whether the rule is allowed or denied

$alwdny = [System.Security.AccessControl.AccessControlType]::Allow

You can then create the access rule

$acr = New-Object -TypeName System.Security.AccessControl.FileSystemAccessRule -ArgumentList $user, $fsr, $alwdny

Get the current ACL

$acl = Get-Acl -Path C:\Test

Add the new rule


$acl.AddAccessRule($acr)

And finally set the ACL on the object

Set-Acl -Path c:\test -AclObject $acl

Over the next few posts I’ll show how to simplify this process with some functions – in a similar way to those you saw recently for working with shares.

File System ACLs – Get-Acl #1 – Retrieving permissions

Following on from the recent set of posts about setting security permissions on shares I thought it about time I looked at the file system security permissions. PowerShell supplies 2 cmdlets, in the core engine, Get-Acl and Set-Acl for workign with permissions. These two cmdlets are part of the Microsoft.PowerShell.Security module.

Many Powershell users shy away from these 2 cmdlets – they do have a reputation for being hard to use.  This series of articles is meant to make these very useful cmdlets more accessible and easier to use.

Get-Acl is the obvious starting point – you need to know what ACLs exist on a given object.

£> Get-Acl -Path c:\test | Format-Table -a


    Directory: C:\

Path Owner                 Access
---- -----                 ------
test RSSURFACEPRO2\Richard BUILTIN\Administrators Allow  FullControl...

The Format-Table is only used to condense the width of the output.

The default display shown above isn’t that helpful so lets try a list display.

£> Get-Acl -Path c:\test | Format-List


Path   : Microsoft.PowerShell.Core\FileSystem::C:\test
Owner  : RSSURFACEPRO2\Richard
Group  : RSSURFACEPRO2\Richard
Access : BUILTIN\Administrators Allow  FullControl
         NT AUTHORITY\SYSTEM Allow  FullControl
         BUILTIN\Users Allow  ReadAndExecute, Synchronize
         NT AUTHORITY\Authenticated Users Allow  Modify, Synchronize
         NT AUTHORITY\Authenticated Users Allow  -536805376
Audit  :
Sddl   : O:S-1-5-21-2502823385-1436278615-3517788930-1001G:S-1-5-21-2502823385-1436278615-3517788930-1001D:AI(A;OICIID;
         FA;;;BA)(A;OICIID;FA;;;SY)(A;OICIID;0x1200a9;;;BU)(A;ID;0x1301bf;;;AU)(A;OICIIOID;SDGXGWGR;;;AU)

That starts to look a bit more useful.  The Access and Sddl properties hold what you need.

If you dive straight into retreiving the Access property:

£> Get-Acl -Path c:\test | select -ExpandProperty Access


FileSystemRights  : FullControl
AccessControlType : Allow
IdentityReference : BUILTIN\Administrators
IsInherited       : True
InheritanceFlags  : ContainerInherit, ObjectInherit
PropagationFlags  : None

etc

You will see an entry similar to the above for each security setting on the object.  What would be simpler to work with is the way the Access property is presented when using Format-List. If you examine the complete object produced by Get-Acl:

£> Get-Acl | Format-List *


PSPath                  : Microsoft.PowerShell.Core\FileSystem::C:\MyData\SkyDrive\Data\scripts
PSParentPath            :

Microsoft.PowerShell.Core\FileSystem::C:\MyData\SkyDrive\Data
PSChildName             : scripts
PSDrive                 : C
PSProvider              : Microsoft.PowerShell.Core\FileSystem
CentralAccessPolicyId   :
CentralAccessPolicyName :

AccessToString          : BUILTIN\Administrators Allow  FullControl
                          NT AUTHORITY\SYSTEM Allow  FullControl
                          BUILTIN\Users Allow  ReadAndExecute, Synchronize
                          NT AUTHORITY\Authenticated Users Allow  Modify, Synchronize
                          NT AUTHORITY\Authenticated Users Allow  -536805376

AuditToString           :
Path                    : Microsoft.PowerShell.Core\FileSystem::C:\MyData\SkyDrive\Data\scripts
Owner                   : RSSURFACEPRO2\Richard
Group                   : RSSURFACEPRO2\Richard
Access                  : {System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule,
                          System.Security.AccessControl.FileSystemAccessRule...}
Sddl                    : O:S-1-5-21-2502823385-1436278615-3517788930-1001G:S-1-5-21-2502823385-1436278615-3517788930-1
                          001D:AI(A;OICIID;FA;;;BA)(A;OICIID;FA;;;SY)(A;OICIID;0x1200a9;;;BU)(A;ID;0x1301bf;;;AU)(A;OIC
                          IIOID;SDGXGWGR;;;AU)
AccessRightType         : System.Security.AccessControl.FileSystemRights
AccessRuleType          : System.Security.AccessControl.FileSystemAccessRule
AuditRuleType           : System.Security.AccessControl.FileSystemAuditRule
AreAccessRulesProtected : False
AreAuditRulesProtected  : False
AreAccessRulesCanonical : True
AreAuditRulesCanonical  : True

What you need to display is the AccessToString property:

£> Get-Acl | select -ExpandProperty AccessToString
BUILTIN\Administrators Allow  FullControl
NT AUTHORITY\SYSTEM Allow  FullControl
BUILTIN\Users Allow  ReadAndExecute, Synchronize
NT AUTHORITY\Authenticated Users Allow  Modify, Synchronize
NT AUTHORITY\Authenticated Users Allow  -536805376

Which gives a very nice summary of the permissions.

If you want to stick with working with objects then use something like this to duplicate the display

Get-Acl |
select -ExpandProperty Access |
select IdentityReference, AccessControlType,FileSystemRights

Remove entry from trusted hosts list

The last variant I want to show is removing a single entry from the list

function remove-trustedhost {
[CmdletBinding()]
param (
[string]$trustedhost,
[string]$computername = $env:COMPUTERNAME
)

if (Test-Connection -ComputerName $computername -Quiet -Count 1) {
  $th = Get-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername |
  select -ExpandProperty TrustedHosts

  if ($th) {
    $ths = $th -split ", |,", 0, "Regex"

    $newth = ($ths -ne $trustedhost) -join ", "
   
    Set-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername -ValueSet @{TrustedHosts = $newth}

  }
  else {
    Write-Warning -Message "No trusted hosts to remove"
  }
 
}
else {
  Write-Warning -Message "$computername is unreachable"
}

}

After testing that you can connect to the remote machine use Get-WsManinstance to pull the current trusted hosts list. Split the list into an array. I’ve used a regular expression fo the delimiter so I catch the cases with and without a space after the comma – I tend to leave a space but other people don’t.

Recreate the string without the entry you don’t want.

Its probable you could use the replace operator for this instead but I wanted to show how to remove an entry from an array.

Use set-WSManInstance to reset the trusted hosts list.

Now you have 4 functions for managing your trusted hosts list – enjoy

Clearing the trusted hosts list with Set-WSManInstance

Sometimes you may just need to clear out all of the current values in the trusted hosts list and start again – especially in a lab environment where you may be experimenting.

function clear-trustedhost {
[CmdletBinding()]
param (
[string]$computername = $env:COMPUTERNAME
)

if (Test-Connection -ComputerName $computername -Quiet -Count 1) {
  Set-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername -ValueSet @{TrustedHosts = ""}
}
else {
  Write-Warning -Message "$computername is unreachable"
}

}

The trick is to use Set-WSManInstance to set the value of the TrustedHosts list to an empty string.  Don’t try and use $null  - it will fail

Adding to the trusted hosts list

Its OK to be able to read the contents of the trusted hosts list but what about adding values to it?

function add-trustedhost {
[CmdletBinding()]
param (
[string]$trustedhost,
[string]$computername = $env:COMPUTERNAME
)

if (Test-Connection -ComputerName $computername -Quiet -Count 1) {
  $th = Get-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername |
  select -ExpandProperty TrustedHosts

  if ($th) {
    $newth = $th + ", $trustedhost"
  }
  else {
    $newth = $trustedhost
  }

  Set-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername -ValueSet @{TrustedHosts = $newth}
}
else {
  Write-Warning -Message "$computername is unreachable"
}

}

The function takes a computername paraneter and a string containing one or more machine names to add to the trusted host list.  This is a singe string so

“server02, servero3, server04”

NOT

“serevr02”, “server03”, “server04”

The current value in th trusted hosts list is retrieved and if it has  a current value the extra trusted host( s ) are added. If the trusted hosts list is empty the new list replaces it,  Set-WSManInstance is used to set the value.

Reading the trusted hosts list

One of the sessions I did at the recent PowerShell summit was on using the WSMAN cmdlets. In my experience, these cmdlets aren’t used much. This is for a couple of reasons I think – the syntax is a bit difficult and there are often other ways to perform the task.

This short series of posts will concentrate on using the WSMAN cmdlets to work with your trusted hosts list.  The trusetd hosts list is used in remoting, especially non-domain remoting or if you need to credssp. to determine which machines your machine trusts.

You can view the trusted hosts list by using the wsman provider:

£> ls WSMan:\localhost\Client\TrustedHosts


   WSManConfig: Microsoft.WSMan.Management\WSMan::localhost\Client

Type            Name                   SourceOfValue   Value
----            ----                   -------------   -----
System.String   TrustedHosts                           server02

if you want just the results

£> ls WSMan:\localhost\Client\TrustedHosts | select -ExpandProperty Value
server02

You can achieve the same result with Get-WSMANinstance:

Get-WSManInstance -ResourceURI winrm/config/client | select -ExpandProperty TrustedHosts

This is a bit much to type regularly so lets create a function:

function get-trustedhost {
[CmdletBinding()]
param (
[string]$computername = $env:COMPUTERNAME
)

if (Test-Connection -ComputerName $computername -Quiet -Count 1) {
  Get-WSManInstance -ResourceURI winrm/config/client -ComputerName $computername |
  select -ExpandProperty TrustedHosts
}
else {
  Write-Warning -Message "$computername is unreachable"
}

}

The function has a single parameter – the computername that defaults to the local machine.

Run Test-Connection to ensure that you can connect to the machine (-Quiet returns a boolean rather than the ping information). If you can connect use Get-WSMANinstance to fetch the trusted hosts data.

If Test-Connection doesn’t contact the remote machine use Write-Warning to output a message.

Share Permissions – setting deny

The last change to the share permissions functions to modify the Set-SharePermissions functions to enable the application of Deny permissions.

The function becomes:

#requires -Version 3.0
function Set-SharePermission {
[CmdletBinding()]
param (
  [Parameter(Mandatory=$true)]
  [string]$sharename,

  [string]$domain = $env:COMPUTERNAME,

  [Parameter(Mandatory=$true)]
  [string]$trusteeName,

  [Parameter(Mandatory=$true)]
  [ValidateSet("Read", "Change", "FullControl")]
  [string]$permission = "Read",

  [string]$computername = $env:COMPUTERNAME,

  [parameter(ParameterSetName="AllowPerm")]
  [switch]$allow,

  [parameter(ParameterSetName="DenyPerm")]
  [switch]$deny
)

switch ($permission) {
   'Read'        {$accessmask = 1179817}
   'Change'      {$accessmask = 1245631}
   'FullControl' {$accessmask = 2032127}
}


$tclass = [wmiclass]"\\$computername\root\cimv2:Win32_Trustee"
$trustee = $tclass.CreateInstance()
$trustee.Domain = $domain
$trustee.Name = $trusteeName

$aclass = [wmiclass]"\\$computername\root\cimv2:Win32_ACE"
$ace = $aclass.CreateInstance()
$ace.AccessMask = $accessmask

switch ($psCmdlet.ParameterSetName) {
"AllowPerm"  {$ace.AceType = 0}
"DenyPerm"  {$ace.AceType = 1}
default {Write-Host "Error!!! Should not be here" }
}

$ace.Trustee = $trustee

$shss = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$sd = Invoke-WmiMethod -InputObject $shss -Name GetSecurityDescriptor |
select -ExpandProperty Descriptor

$sclass = [wmiclass]"\\$computername\root\cimv2:Win32_SecurityDescriptor"
$newsd = $sclass.CreateInstance()
$newsd.ControlFlags = $sd.ControlFlags

foreach ($oace in $sd.DACL){

if (($oace.Trustee.Name -eq $trusteeName) -AND ($oace.Trustee.Domain -eq $domain) ) {
    continue
}
else
{
    $newsd.DACL += $oace
}
}
$newsd.DACL += $ace

$share = Get-WmiObject -Class Win32_LogicalShareSecuritySetting -Filter "Name='$sharename'" -ComputerName $computername
$share.SetSecurityDescriptor($newsd)

} # end function

The changes are to add two switches –allow & –deny.  Put them in different parametersets to ensure mutual exclusivity.

As you are using parametersets you can use a switch based on the parameterset name to set the ACE type.

switch ($psCmdlet.ParameterSetName) {
"AllowPerm"  {$ace.AceType = 0}
"DenyPerm"  {$ace.AceType = 1}
default {Write-Host "Error!!! Should not be here" }
}

Everything else remains the same.