Monthly Archive

Categories

File System

Get Folder sizes

One problem that comes up quite often is how do you get folder sizes. One option is use Measure-Object but the problem with that approach is that its going to be a pretty slow process if you have a lot of folders. PowerShell doesn't have a method of directly getting the folder size and you have to count through all of the sub-folders which becomes a very repetitive exercise.

 

If you're prepared to use an old style VBScript approach you can use the FileSystem COM object like this

function Get-FolderSize { 
 [CmdletBinding()] 
param ( 
  [string]$path = 'C:\MyData' 
 )

if (-not (Test-Path -Path $path)){ 
  Throw "$path - Path not found" 
 }

$fso = New-Object -ComObject "Scripting.FileSystemObject"

Get-ChildItem -Path $path -Directory -Recurse | 
foreach { 
  
  $size = ($fso.GetFolder("$($PSItem.FullName)")).Size 
  
  $props = [ordered]@{ 
    Name = $PSItem.Name 
    Created = $PSItem.CreationTime 
    FilePath = $PSItem.FullName 
    SizeMB = [math]::Round( ($size / 1mb), 2) 
  }

  New-Object -TypeName PSObject -Property $props 
 }

}

 

use as

Get-FolderSize -path <folder path>

 

if you want the subfolders immediately after their parent then add a sort

Get-FolderSize -path <folder path> | sort FilePath

and if you want to order by size then

 

Get-FolderSize -path <folder path>  | sort SizeMB -Descending

Blocksize missing?

I recently had a question asking why the Bloacksize property on Win32_LogicalDisk is empty but is populated on Win32_Volume.

The thing is to remember the underlying thing that these 2 CIM classes represent.

 

A logical disk is a subdivision of an extended partition. An extended partition can hold one or more logical disks.

 

When you create a volume on a disk you use either a primary partition or a logical disk from an extended partition. Until you format the volume you can’t have a block size as that is determined by the parameters you use to perform the format.

 

The documentation for Win32_LogicalDisk states that block size is not populated for logical disks https://msdn.microsoft.com/en-us/library/aa394173(v=vs.85).aspx.

Awkward file and folder names

Spent some time today dealing with a situation where there were special characters – namely [ ] in folder a file names

£> Get-ChildItem -Path C:\Test

    Directory: C:\Test

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
d----        21/01/2015     17:58            Awkward [One]
d----        21/01/2015     17:59            Simple One

 

Each folder has 3 files:

File 1.txt
File 2.txt
File 3.txt

 

Get-ChildItem -Path 'C:\Test\Simple One'

will work and show you the contents. When I’m typing paths like this I let Tab completion do the work. Type c:\test\ and use the Tab key to cycle round the available folders.

 

This gives

£> Get-ChildItem -Path 'C:\Test\Awkward `[One`]'
Get-ChildItem : Cannot find path 'C:\Test\Awkward `[One`]' because it does not exist.
At line:1 char:1
+ Get-ChildItem -Path 'C:\Test\Awkward `[One`]'
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (C:\Test\Awkward `[One`]:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

 

Unfortunately, the construct produced by Tab completion doesn’t work.  You need to double up on the back ticks so that it functions as an escape character.

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]'

 

But that only shows you the folder not the contents.

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]\*'

OR

Get-ChildItem -Path 'C:\Test\Awkward ``[One``]' -Include *

 

Will show you the contents of the folder.

 

But bizarrely

Get-Content -Path 'C:\Test\Awkward `[One`]\File 1.txt'

Works. As does

Copy-Item -Path 'C:\Test\Awkward `[One`]\File 1.txt' -Destination c:\test2

 

By using back ticks and quotes you can get round most problems like this. Other characters that cause similar problems are commas and quote marks.

Best advice of all – don’t use those awkward characters in your file names if you can possibly avoid it.

Finding a file version

Interesting question on the forum – how to find the file version of IE on remote machines?

 

Get-CimInstance -ClassName CIM_DataFile -Filter "Name = 'C:\\Program Files\\Internet Explorer\\iexplore.exe'"  | select -ExpandProperty Version

 

Use the CIM_dataFile class.  Its one of the few CIM_ classes that doesn’t have a Win32_ equivalent.

 

In this case you know the path to the file – note the \\ as you have to escape a single \ in WMI filters

File system ACLS – inheritance

When you look at a FileSystemAccessRule it’llbe something like this:

FileSystemRights  : Modify, Synchronize
AccessControlType : Allow
IdentityReference : NT AUTHORITY\Authenticated Users
IsInherited       : True
InheritanceFlags  : None
PropagationFlags  : None

So far we haven’t dealt with the three inheritance flags.

Isinherited indicates that the permission is inherited from further up the file system tree

The Inheritance flags -  http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.inheritanceflags(v=vs.110).aspx – are from the System.Security.AccessControl.InheritanceFlags enumeration:

None

ContainerInherit – child containers (folders) inherit the permission

ObjectInherit – child leaf objects (files) inherit the permission

The popagation flags are from the System.Security.AccessControl.PropagationFlags enumeration - http://msdn.microsoft.com/en-us/library/system.security.accesscontrol.propagationflags(v=vs.110).aspx

None – no inheritance flags are present

InheritOnly – ACE is propagated to child containers and leaf objects

NoPropagateInherit – specifies the ACE is NOT propagated to child objects

This leads to our function being modified to look like this:

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",

[Parameter(ParameterSetName='NOinherit')]
[switch]$NOinherit,

[Parameter(ParameterSetName='Container')]
[switch]$containerinherit,

[Parameter(ParameterSetName='Object')]
[switch]$objectinherit,

[switch]$deny

)

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

if ($containerinherit -OR $objectinherit) {
$propflag = [System.Security.AccessControl.PropagationFlags]::InheritOnly
}
else {
$propflag = [System.Security.AccessControl.PropagationFlags]::None
}

 

if ($containerinherit) {
$inhflag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit
}

if ($objectinherit) {
$inhflag = [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
}

if ($NOinherit) {
$inhflag = [System.Security.AccessControl.InheritanceFlags]::None
}

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, $inhflag, $propflag, $alwdny

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

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

Examples of use:

add-acl -path C:\Test -trusteeName "$($env:COMPUTERNAME)\NewUser" -permission FullControl -NOinherit
add-acl -path C:\Test -trusteeName "$($env:COMPUTERNAME)\NewUser" -permission FullControl -containerinherit
add-acl -path C:\Test -trusteeName "$($env:COMPUTERNAME)\NewUser" -permission FullControl -objectinherit

Set the permissions on the folder, the subfolders and the files respectively.

If you want all three – run it three times as above

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.

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

CIM snippets–working with file system

The latest instalment from the WMI team on using PowerShell and the CIM cmdlets is available - http://blogs.msdn.com/b/wmi/archive/2014/03/28/performing-management-tasks-using-cim-cmdlets-4-files-and-folders.aspx

This time round the examples are to do with working with the file system – files, folders and shares.

If you’ve worked with WMI you’ll be aware of that very often you get 2 classes one with a prefix of CIM (base class from DMTF definition) and one with Win32 prefix which is the Microsoft implementation. They 2 classes are often identical though the Win32 class may have additions.

The WMI class for working with files is different – it only has a CIM version CIM_DataFile.

The first example in the post is about renaming a file. A much simpler coding of the task would be:

Get-CimInstance -ClassName CIM_Datafile -Filter "Name = 'C:\\Test\\Names.txt'" |
Invoke-CimMethod -MethodName Rename -Arguments @{FileName = 'C:\\Test\\OldNames.txt'}

A couple of points to note:

- when dealing with file paths all \ characters must be doubled. This is because \ is a WMI escape character so you need to escape it to use it literally.

- Invoke-CimMethod uses a hash table for the method arguments with the argument name as the key – this takes away any of the argument order issues you see with Invoke-WmiMethod)

One perennial problem for administrators is users putting their own files on the organization’s file servers.  Want to know if there any files of a specific type in a folder?

Get-CimInstance -ClassName CIM_Datafile -Filter "Extension = 'txt' AND Path = '\\test\\'"

If you leave the path out of the filter then all files on the drive will be searched – could take a while.  Being specific in your filter will save you a lot of time.

Want to find all the mp3 files on a drive?

Get-CimInstance -ClassName CIM_Datafile -Filter "Extension = 'mp3'"

You can’t create files and folders with CIM (or WMI) but you can create shares

$margs = @{
Path = 'C:\Test'
Name = 'Test2April'
Description = 'TestShare'
Type = [uint32]0
}

Invoke-CimMethod -ClassName Win32_Share -MethodName Create -Arguments $margs

Create the hash table of arguments separately  - its easier to read. Bizarrely this time you don’t need to escape the \ in the path

You can see the shares on a system like this:

Get-CimInstance -ClassName Win32_Share

CIM may not be you first port of call when working with the file system but it can be useful – especially on remote systems.

You can find out much more about using CIM to work with the file system in chapetr 8 of PowerShell and WMI – www.manning.com/siddaway2