EncryptData.ps1 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <#
  2. .SYNOPSIS
  3. This PowerShell script is used to create a VeraCrypt container with minimal size to hold a copy of the given input file or directory.
  4. .DESCRIPTION
  5. This script takes as input a file path or directory path and a container path.
  6. If the container path is not specified, it defaults to the same as the input path with a ".hc" extension.
  7. The script calculates the minimal size needed to hold the input file or directory in a VeraCrypt container.
  8. It then creates a VeraCrypt container with the specified path and the calculated size using exFAT filesystem.
  9. Finally, the container is mounted, the input file or directory is copied to the container and the container is dismounted.
  10. .PARAMETER inputPath
  11. The file path or directory path to be encrypted in the VeraCrypt container.
  12. .PARAMETER containerPath
  13. The desired path for the VeraCrypt container. If not specified, it defaults to the same as the input path with a ".hc" extension.
  14. .EXAMPLE
  15. .\EncryptData.ps1 -inputPath "C:\MyFolder" -containerPath "D:\MyContainer.hc"
  16. .\EncryptData.ps1 "C:\MyFolder" "D:\MyContainer.hc"
  17. .\EncryptData.ps1 "C:\MyFolder"
  18. .NOTES
  19. Author: Mounir IDRASSI
  20. Email: mounir.idrassi@idrix.fr
  21. Date: 26 July 2024
  22. License: This script is licensed under the Apache License 2.0
  23. #>
  24. # parameters
  25. param(
  26. [Parameter(Mandatory=$true)]
  27. [string]$inputPath,
  28. [string]$containerPath
  29. )
  30. function ConvertTo-AbsolutePath {
  31. param (
  32. [Parameter(Mandatory=$true)]
  33. [string]$Path
  34. )
  35. if ([System.IO.Path]::IsPathRooted($Path)) {
  36. return $Path
  37. }
  38. return Join-Path -Path (Get-Location) -ChildPath $Path
  39. }
  40. # Convert input path to fully qualified path
  41. $inputPath = ConvertTo-AbsolutePath -Path $inputPath
  42. # Check if input path exists
  43. if (-not (Test-Path $inputPath)) {
  44. Write-Host "The specified input path does not exist. Please provide a valid input path."
  45. exit 1
  46. }
  47. $inputPath = (Resolve-Path -Path $inputPath).Path
  48. # Set container path if not specified
  49. if ([string]::IsNullOrWhiteSpace($containerPath)) {
  50. $containerPath = "${inputPath}.hc"
  51. } else {
  52. $containerPath = ConvertTo-AbsolutePath -Path $containerPath
  53. }
  54. # Check if container path already exists
  55. if (Test-Path $containerPath) {
  56. Write-Host "The specified container path already exists. Please provide a unique path for the new container."
  57. exit 1
  58. }
  59. # Full path to VeraCrypt executables
  60. $veracryptPath = "C:\Program Files\VeraCrypt" # replace with your actual path
  61. $veraCryptExe = Join-Path $veracryptPath "VeraCrypt.exe"
  62. $veraCryptFormatExe = Join-Path $veracryptPath "VeraCrypt Format.exe"
  63. # Constants used to calculate the size of the exFAT filesystem
  64. $InitialVBRSize = 32KB
  65. $BackupVBRSize = 32KB
  66. $InitialFATSize = 128KB
  67. $ClusterSize = 32KB # TODO : make this configurable
  68. $UpCaseTableSize = 128KB # Typical size
  69. function Get-ExFATSizeRec {
  70. param(
  71. [string]$Path,
  72. [uint64] $TotalSize
  73. )
  74. # Constants
  75. $BaseMetadataSize = 32
  76. $DirectoryEntrySize = 32
  77. try {
  78. # Get the item (file or directory) at the provided path
  79. $item = Get-Item -Path $Path -ErrorAction Stop
  80. # Calculate metadata size
  81. $fileNameLength = $item.Name.Length
  82. $metadataSize = $BaseMetadataSize + ($fileNameLength * 2)
  83. # Calculate directory entries
  84. if ($fileNameLength -gt 15) {
  85. $numDirEntries = [math]::Ceiling($fileNameLength / 15) + 1
  86. } else {
  87. $numDirEntries = 2
  88. }
  89. $dirEntriesSize = $numDirEntries * $DirectoryEntrySize
  90. # Add metadata, file size, and directory entries size to $TotalSize
  91. $TotalSize += $metadataSize + $dirEntriesSize
  92. if ($item.PSIsContainer) {
  93. # It's a directory
  94. $childItems = Get-ChildItem -Path $Path -ErrorAction Stop
  95. foreach ($childItem in $childItems) {
  96. # Recursively call this function for each child item
  97. $TotalSize = Get-ExFATSizeRec -Path $childItem.FullName -TotalSize $TotalSize
  98. }
  99. } else {
  100. # It's a file
  101. # Calculate actual file size and round it up to the nearest multiple of $ClusterSize
  102. $fileSize = $item.Length
  103. $totalFileSize = [math]::Ceiling($fileSize / $ClusterSize) * $ClusterSize
  104. # Add metadata, file size, and directory entries size to $TotalSize
  105. $TotalSize += $totalFileSize
  106. }
  107. } catch {
  108. Write-Error "Error processing item at path ${Path}: $_"
  109. }
  110. return $TotalSize
  111. }
  112. function Get-ExFATSize {
  113. param(
  114. [string]$Path
  115. )
  116. try {
  117. # Initialize total size
  118. $totalSize = $InitialVBRSize + $BackupVBRSize + $InitialFATSize + $UpCaseTableSize
  119. # Call the recursive function
  120. $totalSize = Get-ExFATSizeRec -Path $Path -TotalSize $totalSize
  121. # Add the root directory to $totalSize
  122. $totalSize += $ClusterSize
  123. # Calculate the size of the Bitmap Allocation Table
  124. $numClusters = [math]::Ceiling($totalSize / $ClusterSize)
  125. $bitmapSize = [math]::Ceiling($numClusters / 8)
  126. $totalSize += $bitmapSize
  127. # Adjust the size of the FAT
  128. $fatSize = $numClusters * 4
  129. $totalSize += $fatSize - $InitialFATSize
  130. # Add safety factor to account for potential filesystem overhead
  131. # For smaller datasets (<100MB), we add 1% or 64KB (whichever is larger)
  132. # For larger datasets (>=100MB), we add 0.1% or 1MB (whichever is larger)
  133. # This scaled approach ensures adequate extra space without excessive overhead
  134. $safetyFactor = if ($totalSize -lt 100MB) {
  135. [math]::Max(64KB, $totalSize * 0.01)
  136. } else {
  137. [math]::Max(1MB, $totalSize * 0.001)
  138. }
  139. $totalSize += $safetyFactor
  140. # Return the minimum disk size needed to store the exFAT filesystem
  141. return $totalSize
  142. } catch {
  143. Write-Error "Error calculating exFAT size for path ${Path}: $_"
  144. return 0
  145. }
  146. }
  147. # Calculate size of the container
  148. $containerSize = Get-ExFATSize -Path $inputPath
  149. # Convert to MB and round up to the nearest MB
  150. $containerSize = [math]::Ceiling($containerSize / 1MB)
  151. # Add extra space for VeraCrypt headers, reserved areas, and potential alignment requirements
  152. # We use a sliding scale to balance efficiency for small datasets and adequacy for large ones:
  153. # - For very small datasets (<10MB), add 1MB
  154. # - For small to medium datasets (10-100MB), add 2MB
  155. # - For larger datasets (>100MB), add 1% of the total size
  156. # This approach ensures sufficient space across a wide range of dataset sizes
  157. if ($containerSize -lt 10) {
  158. $containerSize += 1 # Add 1 MB for very small datasets
  159. } elseif ($containerSize -lt 100) {
  160. $containerSize += 2 # Add 2 MB for small datasets
  161. } else {
  162. $containerSize += [math]::Ceiling($containerSize * 0.01) # Add 1% for larger datasets
  163. }
  164. # Ensure a minimum container size of 2 MB
  165. $containerSize = [math]::Max(2, $containerSize)
  166. # Specify encryption algorithm, and hash algorithm
  167. $encryption = "AES"
  168. $hash = "sha512"
  169. # Create a SecureString password
  170. $password = Read-Host -AsSecureString -Prompt "Enter your password"
  171. # Create a PSCredential object
  172. $cred = New-Object System.Management.Automation.PSCredential ("username", $password)
  173. Write-Host "Creating VeraCrypt container `"$containerPath`" ..."
  174. # Create file container using VeraCrypt Format
  175. # TODO: Add a switch to VeraCrypt Format to allow specifying the cluster size to use for the container
  176. $veraCryptFormatArgs = "/create `"$containerPath`" /size `"${containerSize}M`" /password $($cred.GetNetworkCredential().Password) /encryption $encryption /hash $hash /filesystem `"exFAT`" /quick /silent"
  177. Start-Process $veraCryptFormatExe -ArgumentList $veraCryptFormatArgs -NoNewWindow -Wait
  178. # Check that the container was successfully created
  179. if (-not (Test-Path $containerPath)) {
  180. Write-Host "An error occurred while creating the VeraCrypt container."
  181. exit 1
  182. }
  183. # Get a list of currently used drive letters
  184. $driveLetter = Get-Volume | Where-Object { $_.DriveLetter -ne $null } | Select-Object -ExpandProperty DriveLetter
  185. # Find the first available drive letter
  186. $unusedDriveLetter = (70..90 | ForEach-Object { [char]$_ } | Where-Object { $_ -notin $driveLetter })[0]
  187. # If no available drive letter was found, print an error message and exit the script
  188. if ($null -eq $unusedDriveLetter) {
  189. # delete the file container that was created
  190. Remove-Item -Path $containerPath -Force
  191. Write-Error "No available drive letters found. Please free up a drive letter and try again."
  192. exit 1
  193. }
  194. Write-Host "Mounting the newly created VeraCrypt container..."
  195. # Mount the container to the chosen drive letter as removable media
  196. Start-Process $veraCryptExe -ArgumentList "/volume `"$containerPath`" /letter $unusedDriveLetter /m rm /password $($cred.GetNetworkCredential().Password) /quit" -NoNewWindow -Wait
  197. # Check if the volume has been mounted successfully
  198. $mountedDriveRoot = "${unusedDriveLetter}:\"
  199. if (-not (Test-Path -Path $mountedDriveRoot)) {
  200. # Volume mount failed
  201. Write-Error "Failed to mount the volume. Please make sure VeraCrypt.exe is working correctly."
  202. # delete the file container that was created
  203. Remove-Item -Path $containerPath -Force
  204. exit 1
  205. }
  206. Write-Host "Copying data to the mounted VeraCrypt container..."
  207. # Copy the file or directory to the mounted drive
  208. if (Test-Path -Path $inputPath -PathType Container) {
  209. # For directories
  210. Copy-Item -Path $inputPath -Destination "$($unusedDriveLetter):\" -Recurse
  211. } else {
  212. # For files
  213. Copy-Item -Path $inputPath -Destination "$($unusedDriveLetter):\"
  214. }
  215. Write-Host "Copying completed. Dismounting the VeraCrypt container..."
  216. # give some time for the file system to flush the data to the disk
  217. Start-Sleep -Seconds 5
  218. # Dismount the volume
  219. Start-Process $veraCryptExe -ArgumentList "/dismount $unusedDriveLetter /quit" -NoNewWindow -Wait
  220. Write-Host "VeraCrypt container created successfully."