Home > Enterprise >  PowerShell create a duplicate folder with zero-size files
PowerShell create a duplicate folder with zero-size files

Time:04-23

I want to create a 0-filesize mirror image of a set of folder, but while robocopy is really good, it doesn't save all of the information that I would like:

robocopy D:\documents E:\backups\documents_$(Get-Date -format "yyyyMMdd_HHmm")\ /mir /create

The /create switch makes each file in the duplicate folder have zero-size, and that is good, but I would like each file in the duplicate folder to have [size] appended to the end of the name with the size in KB or MB or GB, and the create / last modified time on every file to exactly match the original file. This way, I will have a zero-size duplicate of the folder that I can archive, but which contains all of the relevant information for the files in that directory, showing the size of each and the exact create / last modified times.

Are there good / simple ways to iterate through a tree in PowerShell, and for each item create a zero size file with all relevant information like this?

CodePudding user response:

This would be one way to implement the copy command using the approach I mentioned in the comments. I think it's better you work it out on your own for learning's sake - and this example could be improved on wrt handling of parameters. But it should give you something to pull ideas from.

function Copy-FolderZeroSizeFiles {
    [CmdletBinding()]
    param( [Parameter(Mandatory)] [string] $FolderPath, 
            [Parameter(Mandatory)] [string] $DestinationPath )
    # Make sure dest exists.
    New-Item $DestinationPath -Type Directory -Force

    # Ensure dest is not a relative path since we change the working directory.
    $destFullPath = Resolve-Path $DestinationPath
    try {
        # Here we change the working directory of the process.
        Push-Location $FolderPath

        foreach ($item in Get-ChildItem '.' -Recurse) {
            
            $relPath = Resolve-Path $item -Relative

            $type = ($item.Attributes -match 'Directory') ? 'Directory' : 'File'
            $new  = New-Item "$destFullPath\$relPath" -ItemType $type

            $new.Attributes = $item.Attributes
            $new.LastWriteTime = $item.LastWriteTime
        }
    } finally {
        # Silently set process back to its original working directory.
        Pop-Location | Out-Null
    }
}

Here's a function to get the conversion from number of bytes to N.N B/K/M/G format. To get more decimal places, just add 0's to the end of the format strings.

function ConvertTo-FriendlySize($NumBytes) {
    switch ($NumBytes) {
        {$_ -lt 1024}       { "{0,7:0.0}B" -f ($NumBytes)             ; break }
        {$_ -lt 1048576}    { "{0,7:0.0}K" -f ($NumBytes / 1024)      ; break }
        {$_ -lt 1073741824} { "{0,7:0.0}M" -f ($NumBytes / 1048576)   ; break }
        default             { "{0,7:0.0}G" -f ($NumBytes / 1073741824); break }
    }
}

Often, people get these conversions wrong. For instance, it's a common error to use 1024 * 1000 to get Megabytes (which is mixing the base10 value for 1K with the base2 value for 1K) and follow that same logic to get GB and TB.

CodePudding user response:

Here is what I came up with with the additional parts in the question, change $src / $dst as required (D:\VMs is where I keep a lot of Virtual Machines). I have included setting all of CreationTime, LastWriteTime, LastAccessTime so that the backup location with zero-size files is a perfect representation of the source. As I want to use this for archival purposes, I have finally zipped things up and included a date-time stamp in the zipfile name.

# Copy-FolderZeroSizeFiles
$src = "D:\VMs"
$dst = "D:\VMs-Backup"

function ConvertTo-FriendlySize($NumBytes) {
    switch ($NumBytes) {
        {$_ -lt 1024}       { "{0:0.0}B" -f ($NumBytes)             ; break }   # Change {0: to {0,7: to align to 7 characters
        {$_ -lt 1048576}    { "{0:0.0}K" -f ($NumBytes / 1024)      ; break }  
        {$_ -lt 1073741824} { "{0:0.0}M" -f ($NumBytes / 1048576)   ; break }  
        default             { "{0:0.0}G" -f ($NumBytes / 1073741824); break }  
    }
}

function Copy-FolderZeroSizeFiles($FolderPath, $DestinationPath) {
    
    Push-Location $FolderPath
    if (!(Test-Path $DestinationPath)) { New-Item $DestinationPath -Type Directory }

    foreach ($item in Get-ChildItem $FolderPath -Recurse -Force) {
            
        $relPath = Resolve-Path $item.FullName -Relative

        if ($item.Attributes -match 'Directory') {
            $new = New-Item "$DestinationPath\$relPath" -ItemType Directory -Force -EA Silent
        } 
        else {
                $fileBaseName = [System.IO.Path]::GetFileNameWithoutExtension($item.Name)
                $fileExt      = [System.IO.Path]::GetExtension($item.Name)
                $fileSize     = ConvertTo-FriendlySize($item.Length)
                $new = New-Item "$DestinationPath\$(Split-Path $relPath)\$fileBaseName ($fileSize)$fileExt" -ItemType File
        }
        "$($new.Name) : creation $($item.CreationTime), lastwrite $($item.CreationTime), lastaccess $($item.LastAccessTime)"
        $new.CreationTime = $item.CreationTime
        $new.LastWriteTime = $item.LastWriteTime
        $new.LastAccessTime = $item.LastAccessTime
        $new.Attributes = $item.Attributes   # Must this after setting creation/write/access times or get error on Read-Only files
    }
    Pop-Location
}

Copy-FolderZeroSizeFiles $src $dst

$dateTime = Get-Date -Format "yyyyMMdd_HHmm"
$zipName = "$([System.IO.Path]::GetPathRoot($dst))\$([System.IO.Path]::GetFileName($dst))_$dateTime.zip"

Add-Type -AssemblyName System.IO.Compression.FileSystem
[IO.Compression.ZipFile]::CreateFromDirectory($dst, $zipName)
  • Related