Monthly Archive


File System

File times

There are three pairs of file times that are available on files on Windows

PS> Get-ChildItem -Path C:\test\Newoutdata01.txt | select *time*

CreationTime : 14/04/2019 17:28:41
CreationTimeUtc : 14/04/2019 16:28:41
LastAccessTime : 14/04/2019 17:28:41
LastAccessTimeUtc : 14/04/2019 16:28:41
LastWriteTime : 25/02/2019 17:42:49
LastWriteTimeUtc : 25/02/2019 17:42:49


This is how to modify those times

Get-ChildItem -Path C:\test\*.txt |

ForEach-Object {

$date = (Get-Date).AddMonths( -(Get-Random -Maximum 5 -Minimum 1) )

Set-ItemProperty -Path $_.FullName -Name CreationTime -Value $date

Set-ItemProperty -Path $_.FullName -Name LastAccessTime $date.AddDays((Get-Random -Maximum 5 -Minimum 1))

Set-ItemProperty -Path $_.FullName -Name LastWriteTime $date.AddDays((Get-Random -Maximum 8 -Minimum 2))



Use Get-ChildItem to iterate through the files. For each file use Set-Property to set the value on the appropriate property. The values I’m using are purely random – you’d normally use a known value.


The *Utc version of the properties will be automatically set based on its corresponding property

Long file paths

Long file paths – greater than 260 characters – have been a pain to deal with in Windows.


There is an argument that you should avoid file paths of that length but sometimes you don’t have any choice – when you inherit a file structure for instance.


The following cmdlets from the NTFSsecurity module I recently highlighted are designed to work with long file paths.



Hopefully, this will make dealing with long file paths easier in the future

Splitting paths

PowerShell has the Split-Path cmdlet that provides the leaf and parent of a path. But what if you’re splitting paths and need one or paths at a higher level.

Consider the path

PS> $path = 'C:\Scripts\HyperV\Admin\Optimize-VMDisks.ps1'


Its just an arbitrary path from my test machine.

Using Split-Path you can get the parent (by default) or the leaf path

PS> Split-Path -Path $path
PS> Split-Path -Path $path -Parent
PS> Split-Path -Path $path -Leaf


But what if you want the grandfather path. That starts to get ugly just using Split-Path.

PS> Split-Path -Path (Split-Path -Path $path -Parent)


The code gets uglier and uglier as you progress up the path

I decided I needed an object whose properties gave me a consistent view of the path hierarchy – the parent path was always level 1; the grandfather was always level 2 etc. Something like this

Level00 : C:\Scripts\HyperV\Admin\Optimize-VMDisks.ps1
Level01 : C:\Scripts\HyperV\Admin
Level02 : C:\Scripts\HyperV
Level03 : C:\Scripts
Level04 : C:\


My solution was to create this function

function split-multipath {
param (

if (-not (Test-Path -Path $path -IsValid)) {
throw "Invalid path: $path" 

$outpaths = [ordered]@{
Level00 = $path

$l = 1

$path = Split-Path -Path $path -Parent

while ($path -ne ''){
$level = "Level{0:00}" -f $l
$outpaths += @{
$level = $path

$path = Split-Path -Path $path -Parent


New-Object -TypeName PSobject -Property $outpaths



The split-multipath function takes a string as a parameter. That string is tested to determine if its a valid path – that could be moved to a ValidateScript test on the parameter.

The output is created from an ordered hash table. The Level00 property is set to the full path as input.

Set the counter and split the path to get the parent.


Use a while loop to add the current path into the hash table – create the property name with the string format operator –f.

Increment the counter and split the path - again taking the parent.

The while loop runs while the path has data. If you try to split when the path just contains the drive you get an empty string

PS> (Split-Path -Path c:\ -Parent) -eq ''


The data is output as an object so you can access a particular level. For instance if you want the great-grandfather level – you use Level03

PS> $test.Level03


You can use the output object to create a new path based on the level of your choice

PS> Join-Path -Path $test.Level03 -ChildPath Test47

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 { 
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

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``]\*'


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 - – are from the System.Security.AccessControl.InheritanceFlags enumeration:


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 -

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 {
param (
[ValidateScript({Test-Path -Path $_ })]


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






$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

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:

function add-acl {
param (
[ValidateScript({Test-Path -Path $_ })]


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



$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

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 {
param (
[ValidateScript({Test-Path -Path $_ })]

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

     Get-Acl -Path $path | Set-Acl -Path $destination -Passthru -ErrorAction Stop
     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