Monthly Archive

Categories

File System

Count of files in a folder

I was recently left a comment of a post

http://richardspowershellblog.wordpress.com/2009/11/30/updating-access-data/

asking about the how to get the count of files in a folder

There are a number of solutions including dropping back to the FileSystem object from VBscript

If we want just a PowerShell option

function filecount {            
param (            
 [string]$path            
)            
 if (-not (Test-Path $path)){Throw "Path: $path not found"}            
             
 $count = 0            
 $count = Get-ChildItem -Path $path |             
          where {!$_.PSIsContainer} |             
          Measure-Object |            
          select -ExpandProperty count            
                      
 Get-Item -Path $path |             
 select PSDrive,             
 @{N="Parent"; E={($_.PSParentPath -split "FileSystem::")[1]}},            
 Name,            
 @{N="FileCount"; E={$count}}            
             
 Get-ChildItem -Path $path |             
 where {$_.PSIsContainer} |            
 foreach {            
   filecount $($_.Fullname)            
 }            
            
}             
            
filecount "c:\scripts"

Supply a path to the top level folder for the filecount function

It will test if the folder exists & complain if it doesn’t.

We can then count the files in the folder using PSISContainer to filter out any subfolders and measure-object to perform the count.

Get-item is used on the path and piped into select where we split out the parent path and add the file count (that count be done with add-member as well

Get-ChildItem is used on the path and only folders are passed. The path of each subfolder is passed to the filecount function. 

A function calling itself like this known as recursion

Changing folder creation date

A question on the forum asked about setting creation date on folders after they have been copied to match the source folder.

I created a source folder with three folders and modified the creation dates

Set-ItemProperty -Path c:\testsource\folder1 -Name CreationTime -Value ((get-date).adddays(-90))
Set-ItemProperty -Path c:\testsource\folder2 -Name CreationTime -Value ((get-date).adddays(-127))
Set-ItemProperty -Path c:\testsource\folder3 -Name CreationTime -Value ((get-date).adddays(-192))

 

I then created a file

dir c:\scripts |out-file -FilePath test.txt

and copied it into each folder

 

The copy of the folders and their contents is achieved like this

Copy-Item -Path c:\testsource\* -Destination c:\testtarget -Force –Recurse

 

Our source looks like this

PS> Get-ChildItem -Path c:\testsource | where {$_.PSIsContainer} | ft  Fullname, CreationTime -a

FullName                             CreationTime
--------                                    ------------
C:\testsource\Folder1   13/11/2011 11:26:35
C:\testsource\Folder2   07/10/2011 11:27:12
C:\testsource\Folder3   03/08/2011 11:27:27

 

and the target looks like this

PS> Get-ChildItem -Path c:\testtarget | where {$_.PSIsContainer} | ft  Fullname, CreationTime -a

FullName                           CreationTime
--------                                  ------------
C:\testtarget\Folder1   11/02/2012 11:34:25
C:\testtarget\Folder2   11/02/2012 11:34:25
C:\testtarget\Folder3   11/02/2012 11:34:25

 

So our job is to change the creationtime property on the target folders

 

Get-ChildItem -Path c:\testsource -Recurse |             
where {$_.PSIsContainer} |            
foreach {            
 $newfolder = $_.Fullname -replace "Testsource", "Testtarget"            
            
 Set-ItemProperty -Path $newfolder -Name CreationTime -Value $($_.CreationTime)            
}

 

Just loop through the source folders – change the path to match the target and set the CreationTime

 

We can then test the results

PS> Get-ChildItem -Path c:\testtarget | where {$_.PSIsContainer} | ft  Fullname, CreationTime -a

FullName                           CreationTime

--------                                  ------------

C:\testtarget\Folder1   13/11/2011 11:26:35

C:\testtarget\Folder2   07/10/2011 11:27:12

C:\testtarget\Folder3   03/08/2011 11:27:27

 

Job done

Folder information on remote machines

A question in the forums wanted to get the date a folder was changed on their domain controllers. This is one way to do it

$folder = "Common Files"            
            
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() |             
select -ExpandProperty DomainControllers |             
foreach {            
 $dc = $_.Name            
            
 if (Test-Path -Path "\\$dc\c$\Program Files (x86)\$folder") {            
  $data =  Get-Item "\\$dc\c$\Program Files (x86)\$folder" | select Fullname, LastWriteTime            
 }            
 else {            
  $data =  Get-Item "\\$dc\c$\Program Files\$folder" | select Fullname, LastWriteTime            
 }            
 $data | Add-Member -MemberType NoteProperty -Name Server -Value $dc -PassThru            
}

In this case the folder would only be in the 32 bit program files folder so see if “program files (x86)” exists – if so its 64 bit machine & we want that folder. if not we revert to “program files”

Final act is to add the computer name as a parameter – that could also be done as a calculated field in select

Formatting file listings–the other way

Following on from the previous post I was asked if the bit where we set the case on the file names and extension could be done in a select statement.

Simple answer is yes

Get-ChildItem |                        
where {-not $_.PSIsContainer} |                        
sort Fullname |                        
select @{N="FullName"; E={$($_.Fullname.ToUpper())}},                         
@{N="Extension"; E={$($_.Extension.ToLower())}},             
Length, LastWriteTime |            
Format-Table -Autosize

Calculated fields can be used in Format-Table and Select-Object cmdlets

I didn’t mention previously – N is short for Name and E is short for Expression above

Formatting file listings

A question was left asking about displaying a file listing with the full name in upper case and the extension in lower case. Its one line of PowerShell

Get-ChildItem |            
where {-not $_.PSIsContainer} |            
sort Fullname |            
Format-Table @{N="FullName"; E={$($_.Fullname.ToUpper())}},             
@{N="Extension"; E={$($_.Extension.ToLower())}}, Length, LastWriteTime -Autosize

Split-Path & UNC

split-path works with with UNC paths as well as normal paths

 

PS> $path = ""
PS> Split-Path -Path $path -Parent
\\UNCserver\TFSBuilds\componenet\v11.1\XS11.1

 

PS> Split-Path -Path (get-location) -Parent
C:\scripts

Removing empty folders again

Removing empty folders seems to be a question that crops up on a regular basis. The problem is determining if a folder is empty:

  • it could have subfolders
  • it could have zero length files

Both of these cases would register as empty if all we checked was get-childitem and summed the file sizes especially if subfolders are ignored.

One way to remove empty folders is to use the Scripting Object.  Yes back to VBScript days. Those old COM objects have some mighty fine functionality that we might as well use while its there.

$path = "c:\test"            
$fso = New-Object -ComObject "Scripting.FileSystemObject"            
            
$folder = $fso.GetFolder($path)            
            
foreach ($subfolder in $folder.SubFolders){            
             
 if (($subfolder.Files | Measure-Object).Count -gt 0){continue}            
 if (($subFolders.SubFolders  | Measure-Object).Count -gt 0){continue}            
 if ($subfolder.Size -eq 0 ){Remove-Item -Path $($subfolder.Path) -Force -WhatIf}            
            
}

Create a Scripting.FileSystemObject

Get the folder and for each sub folder check the files and subfolders if either are non zero then ignore (go back to top of loop)

if the sub folder size reports as zero then delete it.  I’ve added the whatif parameter so I can easily test.

This only tests one level of subfolders – if we want them all then its time for some recursion

function remove-emptyfolder {            
 param ($folder)            
             
 foreach ($subfolder in $folder.SubFolders){            
             
 $notempty = $false            
 if (($subfolder.Files | Measure-Object).Count -gt 0){$notempty = $true}            
 if (($subFolders.SubFolders  | Measure-Object).Count -gt 0){$notempty = $true}            
 if ($subfolder.Size -eq 0 -and !$notempty){            
   Remove-Item -Path $($subfolder.Path) -Force -WhatIf            
 }            
 else {            
  remove-emptyfolder $subfolder            
 }            
            
}            
            
}            
            
$path = "c:\test"            
$fso = New-Object -ComObject "Scripting.FileSystemObject"            
            
$folder = $fso.GetFolder($path)            
remove-emptyfolder $folder

 

This is based on the previous script except the folder analysis and deletion is in a folder that calls itself if the current folder has subfolders. The test for files and subfolders are used to set a boolean to help determine whether to delete or perform more analysis

Folder and file names

Saw a question in the forums – running Get-ChildItem and wanted the folder containing the file.  Could split PSParent and pick the last element or can use a calculated field

Get-ChildItem -Path c:\test -Recurse |
select FullName, @{N="Folder"; E={Split-Path -Path (Split-Path -Path $_.fullname -Parent) -Leaf  }}

use split-path to get the container and put that into split-path to get the leaf i.e. the last folder in the path.

Jobs a good un!

Modifying favourites

if we use our get-favourite function to look at the content of favourite files

 

get-favourite | select -f 5 | foreach{""; get-content -Path $_.path}

 

we see this sort of structure

[DEFAULT]
BASEURL=http://www.bing.com/
[{000214A0-0000-0000-C000-000000000046}]
Prop3=19,2
[InternetShortcut]
URL=http://www.bing.com/
IDList=
IconFile=http://www.bing.com/favicon.ico
IconIndex=1

 

Not all files have the BASEURL or [DEFAULT] section but they do have the [InternetShortcut]. The other bit we need to consider is the iconfile

We can put together a function to change the URL in a favourite

function set-favourite{             
[CmdletBinding()]             
param(            
[parameter(Mandatory=$true,            
   ValueFromPipeline=$true,             
   ValueFromPipelineByPropertyName=$true)]            
[ValidateNotNullOrEmpty()]            
[string]$path,            
            
[ValidateNotNullOrEmpty()]            
[string]$url            
)            
BEGIN{}#begin             
PROCESS{            
$lines = Get-Content -Path $path            
            
$i = 0            
foreach($line in $lines) {            
             
 if ($line.StartsWith("BASEURL") ){$lines[$i] = "BASEURL=$url"}            
 if ($line.StartsWith("URL") ){$lines[$i] = "URL=$url"}            
 if ($line.StartsWith("IconFile") ){$lines[$i] = "IconFile=$url/favicon.ico"}            
 $i++             
}            
            
Set-Content -Value $lines -Path $path            
            
}#process             
END{}#end            
            
<# 
.SYNOPSIS
Changes URL of a favourite

.DESCRIPTION
Changes URL of a favourite


.EXAMPLE
get-favourite | where {$_.Name -like "google"} | set-favourite -url "www.bing.com"

#>            
            
}

The function can be used as follows

get-favourite | where {$_.Name -like "google"} | set-favourite -url www.bing.com

 

Next job is to create a favourite from scratch

Viewing favourites

I recently copied my favourites between machines which started me thinking about viewing favourites. Like many people I have generated a lot of favourites over the years – do I really need them all. Don’t know because I don’t know what they are.  Its easy to correct that

$favs = New-Object -ComObject Shell.Application            
Get-ChildItem -Path $favs.NameSpace(0x6).Self.Path -Recurse |            
where {-not $_.PSIsContainer} |            
foreach {            
 $fav = New-Object -TypeName PSObject -Property @{            
  Name = $_.BaseName             
 }            
             
 Get-Content -Path $_.FullName | foreach {            
  if ($_.StartsWith("URL=")) {            
   $fav | Add-Member -MemberType NoteProperty -Name URL -Value $_.Replace("URL=","")}            
 }             
 $fav            
}

The script use the Shell COM object to access the favourites special folder. We then iterate through the favourites and get the content of each favourite. These are just text files so we can pick out the URL. An object is created to hold the favourite name and URL and then displayed