function Split-File
{
<#
.SYNOPSIS
Splits a file into multiple parts
.DESCRIPTION
Splits a file into smaller parts. The maximum size of the part files can be specified. The number of parts required is calculated.
.EXAMPLE
Split-File -Path 'c:\test.zip' -PartSizeBytes 2.5MB
Splits the file c:\test.zip in as many parts as necessary. Each part file is no larger than 2.5MB
.EXAMPLE
Split-File -Path 'c:\test.zip' -PartSizeBytes 2.5MB -AddSelfExtractor
Splits the file c:\test.zip in as many parts as necessary. Each part file is no larger than 2.5MB
Adds a powershell script that joins the parts when run, and adds a shortcut file to
run the PowerShell extractor script on double-click, essentially adding a self-extractor
#>
param
(
# Path to the file you want to split
[Parameter(Mandatory,HelpMessage='Path to the file you want to split')]
[String]
$Path,
# maximum size of file chunks (in bytes)
[int]
$PartSizeBytes = 1MB,
# when specified, add a an extractor script and link file to easily convert
# chunks back into the original file
[Switch]
$AddSelfExtractor
)
try
{
# get the path parts to construct the individual part
# file names:
$fullBaseName = [IO.Path]::GetFileName($Path)
$baseName = [IO.Path]::GetFileNameWithoutExtension($Path)
$parentFolder = [IO.Path]::GetDirectoryName($Path)
$extension = [IO.Path]::GetExtension($Path)
# get the original file size and calculate the
# number of required parts:
$originalFile = New-Object -TypeName System.IO.FileInfo -ArgumentList ($Path)
$totalChunks = [int]($originalFile.Length / $PartSizeBytes) + 1
$digitCount = [int][Math]::Log10($totalChunks) + 1
# read the original file and split into chunks:
$reader = [IO.File]::OpenRead($Path)
$count = 0
$buffer = New-Object -TypeName Byte[] -ArgumentList $PartSizeBytes
$moreData = $true
# read chunks until there is no more data
while($moreData)
{
# read a chunk
$bytesRead = $reader.Read($buffer, 0, $buffer.Length)
# create the filename for the chunk file
$chunkFileName = "$parentFolder\$fullBaseName.{0:D$digitCount}.part" -f $count
Write-Verbose -Message "saving to $chunkFileName..."
$output = $buffer
# did we read less than the expected bytes?
if ($bytesRead -ne $buffer.Length)
{
# yes, so there is no more data
$moreData = $false
# shrink the output array to the number of bytes
# actually read:
$output = New-Object -TypeName Byte[] -ArgumentList $bytesRead
[Array]::Copy($buffer, $output, $bytesRead)
}
# save the read bytes in a new part file
[IO.File]::WriteAllBytes($chunkFileName, $output)
# increment the part counter
++$count
}
# done, close reader
$reader.Close()
# add self-extractor
if ($AddSelfExtractor)
{
Write-Verbose -Message "Adding extractor scripts..."
# define the self-extractor powershell script:
$extractorName = "${fullBaseName}.{0:D$digitCount}.part.ps1" -f $count
$extractorPath = Join-Path -Path $parentFolder -ChildPath $extractorName
$filePath = '$PSScriptRoot\' + "$baseName$extension"
# define the self-extractor shortcut file that launches
# the powershell script on double-click:
$linkName = "Extract ${fullBaseName}.lnk"
$linkPath = Join-Path -Path $parentFolder -ChildPath $linkName
# this will be used inside the extractor script to find the
# part files via relative path:
$currentFile = '"$PSCommandPath"'
$currentFolder = '"$PSScriptRoot"'
# write the extractor script source code to file:
"
# copy the join-file source code into the extractor script:
function Join-File {
${function:Join-File}
}
# join the part files and delete the part files after joining:
Join-File -Path ""$filePath"" -Verbose -DeletePartFiles
# remove both extractor scripts:
(Join-Path -Path $currentFolder -ChildPath '$linkName') | Remove-Item
Remove-Item -Path $currentFile
# open the extracted file in windows explorer
explorer.exe ""/select,""""$filepath""""""
" | Set-Content -Path $extractorPath
# create a shortcut file that launches the extractor script
# when it is double-clicked:
$shell = New-Object -ComObject WScript.Shell
$scut = $shell.CreateShortcut($linkPath)
$scut.TargetPath = "powershell.exe"
$scut.Arguments = "-nop -executionpolicy bypass -file ""$extractorPath"""
$scut.WorkingDirectory = ""
$scut.IconLocation = "$env:windir\system32\shell32.dll,162"
$scut.Save()
}
}
catch
{
throw "Unable to split file ${Path}: $_"
}
}