Skip to content

Instantly share code, notes, and snippets.

@joshooaj
Last active March 18, 2025 19:08
Show Gist options
  • Save joshooaj/c52325086415650e52cb8d9fe81aafa4 to your computer and use it in GitHub Desktop.
Save joshooaj/c52325086415650e52cb8d9fe81aafa4 to your computer and use it in GitHub Desktop.
Call the GetDiskFreeSpaceExW function from PowerShell using p/invoke
<#
.SYNOPSIS
Gets the free space on a disk and waits 500ms or for a specified delay before checking again.
.DESCRIPTION
This function uses p/invoke to call the GetDiskFreeSpaceEx function from the kernel32.dll library to get the free
space on a disk based on the path provided. It will keep checking the free space at the specified interval, or 500ms
by default, until an error occurs.
When an error occurs, the function will include the output of GetLastError and the string-formatted
error message.
.PARAMETER Path
Specifies a path to the drive to check the free space on. For example "C:\".
.EXAMPLE
.\watch-diskfreespace.ps1 -Path "C:\"
This example will check the free space on the C: drive and print the results to the console. It will check every 500
milliseconds.
.EXAMPLE
.\watch-diskfreespace.ps1 -Path "C:\" -DelayMS 100
This example will check the free space on the C: drive and print the results to the console. It will check every 100
milliseconds, or 10 times per second.
.EXAMPLE
.\watch-diskfreespace.ps1 -Path "C:\" | Export-Csv watch.csv -NoTypeInformation
This example will check the free space on the C: drive and save the results to a CSV file. It will check every
500 milliseconds until an error occurs.
#>
param(
[Parameter(Mandatory, Position = 0)]
[string]
$Path,
[Parameter()]
[int]
$DelayMS = 500
)
function Get-DiskFreeSpace {
[CmdletBinding()]
param (
[Parameter(Mandatory, Position = 0)]
[string]
$Path
)
begin {
Add-Type -ErrorAction SilentlyContinue -Name 'DiskInfo' -Namespace 'Helpers' -MemberDefinition @"
[DllImport("kernel32.dll", SetLastError=true)]
public static extern bool GetDiskFreeSpaceEx(string lpDirectoryName,
out ulong lpFreeBytesAvailable,
out ulong lpTotalNumberOfBytes,
out ulong lpTotalNumberOfFreeBytes);
[DllImport("kernel32.dll")]
public static extern long GetLastError();
[DllImport("kernel32.dll")]
public static extern long FormatMessage(
long dwFlags,
object lpSource,
long dwMessageId,
long dwLanguageId,
System.Text.StringBuilder lpBuffer,
long nSize,
object Arguments
);
public static string GetFormattedMessage(long errorCode) {
long FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
long FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200;
long flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
System.Text.StringBuilder buffer = new System.Text.StringBuilder(256);
long result = FormatMessage(flags, null, errorCode, 0, buffer, buffer.Capacity, null);
return buffer.ToString();
}
"@
}
process {
try {
$result = [pscustomobject]@{
Timestamp = Get-Date
Path = $Path
FreeBytesAvailable = 0
TotalBytesAvailable = 0
TotalBytesFree = 0
LastError = 0
ErrorMessage = $null
}
[uint64]$freeToCaller, [uint64]$totalAvailable, [uint64]$totalFree = 0
if ([Helpers.DiskInfo]::GetDiskFreeSpaceEx($Path, [ref]$freeToCaller, [ref]$totalAvailable, [ref]$totalFree)) {
$result.FreeBytesAvailable = $freeToCaller
$result.TotalBytesAvailable = $totalAvailable
$result.TotalBytesFree = $totalFree
} else {
$lastError = [Helpers.DiskInfo]::GetLastError()
$errorMessage = [Helpers.DiskInfo]::GetFormattedMessage($lastError)
Write-Error "Call to GetDiskFreeSpaceEx failed. LastError: $lastError, $errorMessage"
$result.LastError = $lastError
$result.ErrorMessage = $errorMessage
}
$result
} catch {
throw
}
}
}
do {
$info = Get-DiskFreeSpace -Path $Path
$info
Start-Sleep -Milliseconds $DelayMS
} while (!$info.LastError)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment