Categories

10572

Touching files

Unix has a command called touch that allows you to set the access time on a file.  PowerShell doesn’t have a direct equivalent but it is very easy to perform the same task:

$date = (Get-Date).AddMonths(-2)
Get-ChildItem -Path C:\Teszzt2 -Filter f*.txt |
Set-ItemProperty -Name LastWriteTime -Value $date -PassThru |
Set-ItemProperty -Name LastAccessTime -Value $date -PassThru |
Set-ItemProperty -Name CreationTime -Value $date

 

Set the date you want.  Get the files and pipe into Set-ItemProperty.  The example shows LastWriteTime, LastAccessTime and CreationTime all being modified. Change the code to just change what you need.

You can see the results

Get-ChildItem -Path C:\Teszzt2 -Filter f*.txt |
select Name, LastAccessTime, LastWriteTime, CreationTime

The simple touch

A utility called touch has been used for many years to modify the creation, access or write time property on a file. The System.Io.FileInfo class enables us to do this. It gets easier because Get-ChildItem returns – guess what - System.Io.FileInfo

The following properties can be modified

CreationTime
LastAccessTime
LastWriteTime

We can create a function to do this

 

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028

function set-filetime{ 
[CmdletBinding()] 
param ( 
[parameter(Mandatory = $true,
 
           ValueFromPipeline
=$true,
 
           ValueFromPipelineByPropertyName
=$true)] 
[string]$path,
[datetime]$date = (Get-Date),
[switch]$creation,
[switch]$access,
[switch]$write
 
) 

PROCESS
{
 
if (Test-Path $path
) {
 
$file = Get-ChildItem -Path $path
  if ($creation){$file.CreationTime = $date
}
 
if ($access){$file.LastAccessTime = $date
}
 
if ($write){$file.LastWriteTime = $date
}
 }
 
else
 {
  
Throw "File $path not found"
 }

}
#process


}
New-Alias -Name touch -Value set-filetime

Setting an alias makes it less typing to use from the command line

Some examples of using it

touch -path d1.txt -creation -access -write                                                                                                   
touch -path d1.txt -write -date (get-date).AddDays(-30)                                                                                       
touch -path d1.txt -access -date (get-date).AddDays(-15)                                                                                      
touch -path d1.txt -creation -date (get-date).AddDays(-50)  

PSCX: Set-FileTime

The PowerShell Community Extensions have a cmdlet Set-FileTime that can do the same thing we did in a script earlier.  I have rewritten the script to use the cmdlet

001
002
003
004
005
006
007
008
009
010
011
012
013
014
Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | 
Select Name, CreationTime, LastWriteTime

Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | foreach {
    $oldname =  "$_" -replace ".xlsx", ".csv"
    $oldfile = Join-Path -Path c:\test\csvtests -ChildPath $oldname
    if (Test-Path -Path $oldfile) {
        $old = Get-Item -Path $oldfile
        Set-FileTime -Time $($old.CreationTime) -Created -Path $($_.Fullname) -Force -Verbose
    }
}

Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | 
Select Name, CreationTime, LastWriteTime

Pretty much the same as before except we save a couple of lines by using the cmdlet.

Technorati Tags: ,,

Change File Dates

Interesting question on the forums about changing the date of files.  Poster was converting a bunch of video files between formats and wanted the file in the new format to have the creation date of the original file.

I had some csv files that i could convert to excel files that mimic the problem

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | 
Select Name, CreationTime, LastWriteTime

Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | foreach {
    $oldname =  "$_" -replace ".xlsx", ".csv"
    $oldfile = Join-Path -Path c:\test\csvtests -ChildPath $oldname
    if (Test-Path -Path $oldfile) {
        $old = Get-Item -Path $oldfile
        $new = Get-Item $_.Fullname
        $new.CreationTime = $old.CreationTime
    }
}

Get-ChildItem -Path C:\Test\csvtests -Filter "*.xlsx" | 
Select Name, CreationTime, LastWriteTime

Check the date on the new files.

Get the new files and for each file replace the extension with that of the old file (I am assuming the name stays the same!). Test the old file exists and if so set the new file creation date to the old file creation date.

A final test with get-childitem shows the change.

 

PowerShell Community Extensions has a Set-FileTime cmdlet that can do this

 

Technorati Tags: ,

Unblocking files

When I download files from the Internet to Vista or Windows 7 the file is blocked and I can’t execute it until its unblocked. This is especially annoying with zip files because if I unzip them all the contents are blocked. I have been meaning to do something in PowerShell for this and finally got round to it.  The blocking is because of a zone identifier in an alternate data stream (ADS) attached to the file. If you have been around PowerShell for any length of time you may remember MoWs post on this subject - http://thepowershellguy.com/blogs/posh/archive/2007/01/27/powershell-accessing-alternative-data-streams-of-files-on-an-ntfs-volume.aspx

This was my starting point but when went to download the file containing the .NET classes we need to access the ADS from http://www.codeproject.com/KB/cs/ntfsstreams.aspx I found that the classes had been updated. So first off download the .NET stuff and extract the dll containing the classes (after unblocking the zip file).

I decided to add this functionality into my module for working with files.

First point is that I need to load the .NET assemblies from the dll.  I can do this in the .psd1 file by adding this line

RequiredAssemblies = 'Trinet.Core.IO.Ntfs.dll'

I then added two functions into the module. One to test if a zone identifier exists and another to delete it

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
function Test-ZoneIdentifier {
    param (
        [Parameter(ValueFromPipeline=$true)] $file
    )
    if (Test-Path $file) {
       
        if ([Trinet.Core.IO.Ntfs.FileSystem]::AlternateDataStreamExists($file, 'Zone.Identifier')){
           
            $fs = [Trinet.Core.IO.Ntfs.FileSystem]::GetAlternateDataStream($file, 'Zone.Identifier')
            $ads = ($fs.OpenText()).ReadToEnd()
            $type = $ads.SubString( ($ads.IndexOf("ZoneId=") + 7), 1) -as [ZoneIdentifier]
           
            Write-Host "Zone.Identifier present. Type $type If of Type Internet the file will be blocked."
        }
        else {[Trinet.Core.IO.Ntfs.FileSystem]::ListAlternateDataStreams($file)}  ## test if other ADS
    }
    else {Write-Host "File Not found"}
}

function Remove-ZoneIdentifier {
    param (
        [Parameter(ValueFromPipeline=$true)] $file
    )
    if (Test-Path $file) {
        if ([Trinet.Core.IO.Ntfs.FileSystem]::AlternateDataStreamExists($file, 'Zone.Identifier')){
            $fs = [Trinet.Core.IO.Ntfs.FileSystem]::GetAlternateDataStream($file, 'Zone.Identifier')
            $fs.Delete()
        }
    }
    else {Write-Host "File Not found"}
}

 

The Test-ZoneIdentifier function will accept a file from the pipeline and test the path to the file.  If the file exists the function will then test to see if a Zone.Identifer ADS exists. If it does it will read the ADS and work out the zone using the ZoneIdentifier enum which we create in the module as well.

001
002
003
004
005
006
007
008
009
$values = "NoZone = -1", "MyComputer = 0", "Intranet = 1", "Trusted = 2", "Internet = 3", "Untrusted = 4"
$code = @"
  public enum ZoneIdentifier : int
  {
      $($values -join ",`n")
  }
"@


Add-Type $code

 

This is adapted from MoWs code at http://thepowershellguy.com/blogs/posh/archive/2008/06/02/powershell-v2-ctp2-making-custom-enums-using-add-type.aspx

A hash table could also be used at this point if preferred.

The function Remove-ZoneIdentifier will delete the ADS and unblock the file.  By enabling these functions to work on the pipeline we can process a number of files in one hit.

The functions are used as follows

PS> Import-Module filefunctions
PS> Get-ChildItem -Path c:\test\NtfsStreams.zip | Test-ZoneIdentifier
Zone.Identifier present. Type Internet  If of Type Internet the file will be blocked.
PS> Get-ChildItem -Path c:\test\NtfsStreams.zip | Remove-ZoneIdentifier
True
PS> Get-ChildItem -Path c:\test\NtfsStreams.zip | Test-ZoneIdentifier

I’ve uploaded the module files to my skydrive at http://cid-43cfa46a74cf3e96.skydrive.live.com/self.aspx/PowerShell%20Scripts/FileFunctions.zip

 

The ntfs streams dll you will need to download separately.

Hidden Files

The last post got me thinking about hidden files and file attributes and how we can manipulate them in PowerShell.  Lets start by creating a PowerShell script.

PS> "'hello world'" > test.ps1
PS> ./test.ps1

We can see the file in a directory listing

PS> Get-ChildItem test.ps1

    Directory: C:\scripts

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        30/05/2009     13:57         32 test.ps1

 

We can view the file attributes

PS> Get-ItemProperty -Path test.ps1 -Name Attributes

PSPath       : Microsoft.PowerShell.Core\FileSystem::C:\scripts\test.ps1
PSParentPath : Microsoft.PowerShell.Core\FileSystem::C:\scripts
PSChildName  : test.ps1
PSDrive      : C
PSProvider   : Microsoft.PowerShell.Core\FileSystem
Attributes   : Archive

We can set the attributes so the file becomes hidden using the Fileattributes enumeration

PS> Set-ItemProperty -Path test.ps1 -Name Attributes -Value ([System.IO.FileAttributes]::Hidden)

as shown previously we have to use the force to get a listing

PS> Get-ChildItem test.ps1 -Force

    Directory: C:\scripts

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
---h-        30/05/2009     13:57         32 test.ps1

But we have over written the existing attributes – oops.  Need to recreate the file and try again.  This time we will add the attribute.

PS> Set-ItemProperty -Path test.ps1 -Name Attributes -Value ((Get-ItemProperty -Path test.ps1).Attributes -bxor [System.IO.FileAttributes]::Hidden)

and we now see the existing attributes are preserved


PS> Get-ChildItem test.ps1 -Force

    Directory: C:\scripts

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a-h-        30/05/2009     14:11         32 test.ps1

We can clear all the attributes or just rest to those we require

PS> Set-ItemProperty -Path test.ps1 -Name Attributes -Value ([System.IO.FileAttributes]::Normal) -Force
PS> Get-ChildItem test.ps1 -Force

    Directory: C:\scripts

Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-----        30/05/2009     14:11         32 test.ps1

The full range of attributes can be seen

PS> [enum]::GetNames([System.IO.FileAttributes])
ReadOnly
Hidden
System
Directory
Archive
Device
Normal
Temporary
SparseFile
ReparsePoint
Compressed
Offline
NotContentIndexed
Encrypted

Normal rules still apply so we can’t compress an encrypted file