Monthly Archive

Categories

File System

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.

Copy-Item2
Get-ChildItem2
Get-FileHash2
Get-Item2
Move-Item2
Remove-Item2
Test-Path2

 

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
C:\Scripts\HyperV\Admin
PS> Split-Path -Path $path -Parent
C:\Scripts\HyperV\Admin
PS> Split-Path -Path $path -Leaf
Optimize-VMDisks.ps1

 

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)
C:\Scripts\HyperV

 

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 {
[CmdletBinding()]
param (
[string]$path
)

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
}

$l++
$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 ''
True

 

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
C:\Scripts

 

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
C:\Scripts\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 { 
 [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.