NtFileFunctions.ps1

# Copyright 2021 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

<#
.SYNOPSIS
Get the NT path for a dos path.
.DESCRIPTION
This cmdlet gets the full NT path for a specified DOS path.
.PARAMETER FullName
The DOS path to convert to NT.
.PARAMETER Resolve
Resolve relative paths to the current PS directory.
.PARAMETER DeviceGuid
Get native path from a Device Interface GUID.
.INPUTS
string[] List of paths to convert.
.OUTPUTS
string Converted path
.EXAMPLE
Get-NtFilePath c:\Windows
Get c:\windows as an NT file path.
.EXAMPLE
Get-ChildItem c:\windows | Get-NtFilePath
Get list of NT file paths from the pipeline.
#>

function Get-NtFilePath {
    [CmdletBinding(DefaultParameterSetName="FromPath")]
    Param(
        [alias("Path")]
        [parameter(Mandatory = $true, Position = 0, ValueFromPipeline, valueFromPipelineByPropertyName, ParameterSetName="FromPath")]
        [string]$FullName,
        [parameter(ParameterSetName="FromPath")]
        [switch]$Resolve,
        [parameter(Mandatory = $true, ParameterSetName="FromGuid")]
        [guid[]]$DeviceGuid
    )

    PROCESS {
        if ($PSCmdlet.ParameterSetName -eq "FromPath") {
            $type = [NtApiDotNet.NtFileUtils]::GetDosPathType($FullName)
            $p = $FullName
            if ($Resolve) {
                if ($type -eq "Relative" -or $type -eq "Rooted") {
                    $p = Resolve-Path -LiteralPath $FullName
                }
            }
            try {
                $p = [NtObjectManager.Utils.PSUtils]::ResolveWin32Path($PSCmdlet.SessionState, $p)
                Write-Output $p
            } catch {
                Write-Error $_
            }
        } elseif ($PSCmdlet.ParameterSetName -eq "FromGuid") {
            foreach($g in $DeviceGuid) {
                [NtApiDotNet.Win32.Device.DeviceUtils]::GetDeviceInterfaceList($g) | Get-NtFilePath | Write-Output
            }
        }
    }
}

<#
.SYNOPSIS
Get the NT path type for a dos path.
.DESCRIPTION
This cmdlet gets the NT path type for a specified DOS path.
.PARAMETER FullName
The DOS path to convert to NT.
.INPUTS
string[] List of paths to convert.
.OUTPUTS
NtApiDotNet.RtlPathType
.EXAMPLE
Get-NtFilePathType c:\Windows
Get the path type for c:\windows.
#>

function Get-NtFilePathType {
    Param(
        [parameter(Mandatory, Position = 0)]
        [string]$FullName
    )

    [NtApiDotNet.NtFileUtils]::GetDosPathType($FullName)
}

<#
.SYNOPSIS
Create a new EA buffer object for use with files.
.DESCRIPTION
This cmdlet creates a new extended attributes buffer object to set on file objects with the SetEa method or with New-NtFile.
.PARAMETER Entries
Optional Hashtable containing entries to initialize into the EA buffer.
.PARAMETER $ExistingBuffer
An existing buffer to initialize the new buffer from.
.INPUTS
None
.OUTPUTS
NtApiDotNet.EaBuffer
.EXAMPLE
New-NtEaBuffer
Create a new empty EaBuffer object
.EXAMPLE
New-NtEaBuffer @{ INTENTRY = 1234; STRENTRY = "ABC"; BYTEENTRY = [byte[]]@(1,2,3) }
Create a new EaBuffer object initialized with three separate entries.
#>

function New-NtEaBuffer {
    [CmdletBinding(DefaultParameterSetName = "FromEntries")]
    Param(
        [Parameter(ParameterSetName = "FromEntries", Position = 0)]
        [Hashtable]$Entries = @{ },
        [Parameter(ParameterSetName = "FromExisting", Position = 0)]
        [NtApiDotnet.Eabuffer]$ExistingBuffer
    )

    if ($null -eq $ExistingBuffer) {
        $ea_buffer = New-Object NtApiDotNet.EaBuffer
        foreach ($entry in $Entries.Keys) {
            $ea_buffer.AddEntry($entry, $Entries.Item($entry), 0)
        }
        return $ea_buffer
    }
    else {
        return New-Object NtApiDotNet.EaBuffer -ArgumentList $ExistingBuffer
    }
}

<#
.SYNOPSIS
Add an entry to an existing EA buffer.
.DESCRIPTION
This cmdlet adds a new extended attributes entry to a buffer.
.PARAMETER Buffer
The EA buffer to add to.
.PARAMETER Byte
The bytes to add.
.PARAMETER Byte
The bytes to add.
.PARAMETER Byte
The bytes to add.
.PARAMETER Byte
The bytes to add.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Add-NtEaBuffer -Buffer $ea -Name "ABC" -Byte @(0, 1, 2, 3)
Add an entry with name ABC and a set of bytes.
.EXAMPLE
Add-NtEaBuffer -Buffer $ea -Name "ABC" -String "Hello"
Add an entry with name ABC and a string.
.EXAMPLE
Add-NtEaBuffer -Buffer $ea -Name "ABC" -Int 1234
Add an entry with name ABC and an integer.
#>

function Add-NtEaBuffer {
    [CmdletBinding(DefaultParameterSetName="FromString")]
    Param(
        [Parameter(Mandatory, Position = 0)]
        [NtApiDotNet.Eabuffer]$EaBuffer,
        [Parameter(Mandatory, Position = 1)]
        [string]$Name,
        [Parameter(Mandatory, Position = 2, ParameterSetName="FromString")]
        [string]$String,
        [Parameter(Mandatory, Position = 2, ParameterSetName="FromBytes")]
        [byte[]]$Byte,
        [Parameter(Mandatory, ParameterSetName="FromInt")]
        [int]$Int,
        [NtApiDotNet.EaBufferEntryFlags]$Flags = 0
    )
    switch($PSCmdlet.ParameterSetName) {
        "FromString" {
            $EaBuffer.AddEntry($Name, $String, $Flags)
        }
        "FromBytes" {
            $EaBuffer.AddEntry($Name, $Byte, $Flags)
        }
        "FromInt" {
            $EaBuffer.AddEntry($Name, $Int, $Flags)
        }
    }
}

<#
.SYNOPSIS
Starts a file oplock with a specific level.
.DESCRIPTION
This cmdlet starts a file oplock with a specific level.
.PARAMETER File
The file to oplock on.
.PARAMETER Level
The oplock level to start.
.PARAMETER LeaseLevel
The oplock lease level to start.
.PARAMETER Flags
Flags for the oplock lease.
.PARAMETER Async
Specify to return an asynchronous task which can be waited on with Wait-AsyncTaskResult.
.INPUTS
None
.OUTPUTS
None or NtApiDotNet.RequestOplockOutputBuffer if using LeaseLevel. If Async then a Task.
.EXAMPLE
Start-NtFileOplock $file -Exclusive
Start an exclusive oplock.
.EXAMPLE
Start-NtFileOplock $file -Level Level1
Start a level 1 oplock.
.EXAMPLE
Start-NtFileOplock $file -LeaseLevel Read,Handle
Start a "lease" oplock with Read and Handle levels.
#>

function Start-NtFileOplock {
    [CmdletBinding(DefaultParameterSetName = "OplockLevel")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, ParameterSetName = "OplockExclusive")]
        [switch]$Exclusive,
        [parameter(Mandatory, Position = 1, ParameterSetName = "OplockLevel")]
        [NtApiDotNet.OplockRequestLevel]$Level,
        [parameter(Mandatory, ParameterSetName = "OplockLease")]
        [NtApiDotNet.OplockLevelCache]$LeaseLevel,
        [parameter(ParameterSetName = "OplockLease")]
        [NtApiDotNet.RequestOplockInputFlag]$Flags = "Request",
        [switch]$Async
    )

    $result = switch ($PSCmdlet.ParameterSetName) {
        "OplockExclusive" {
            if ($Async) {
                $File.OplockExclusiveAsync()
            } else {
                $File.OplockExclusive()
            }
        }
        "OplockLevel" {
            if ($Async) {
                $File.RequestOplockAsync($Level)
            } else {
                $File.RequestOplock($Level)
            }
        }
        "OplockLease" {
            if ($Async) {
                $File.RequestOplockLeaseAsync($LeaseLevel, $Flags)
            } else {
                $File.RequestOplockLease($LeaseLevel, $Flags)
            }
        }
    }

    $result | Write-Output
}

<#
.SYNOPSIS
Acknowledges a file oplock break.
.DESCRIPTION
This cmdlet acknowledges a file oplock break with a specific level.
.PARAMETER File
The file to acknowledge the break on.
.PARAMETER Level
The oplock acknowledge level.
.PARAMETER Lease
Acknowledge a lease oplock and reduce level to None.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Confirm-NtFileOplock $file -Level Acknowledge
Acknowledge an oplock break.
.EXAMPLE
Confirm-NtFileOplock $file -LeaseLevel Read
Acknowledge to a read oplock.
#>

function Confirm-NtFileOplock {
    [CmdletBinding(DefaultParameterSetName = "OplockLevel")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 1, ParameterSetName = "OplockLevel")]
        [NtApiDotNet.OplockAcknowledgeLevel]$Level,
        [parameter(Mandatory, Position = 1, ParameterSetName = "OplockLease")]
        [switch]$Lease,
        [parameter(ParameterSetName = "OplockLease")]
        [switch]$CompleteOnClose
    )

    switch ($PSCmdlet.ParameterSetName) {
        "OplockLevel" {
            $File.AcknowledgeOplock($Level)
        }
        "OplockLease" {
            $File.AcknowledgeOplockLease($CompleteOnClose)
        }
    }
}

<#
.SYNOPSIS
Get the EA buffer from a file.
.DESCRIPTION
This cmdlet queries for the Extended Attribute buffer from a file by path or from a NtFile object.
.PARAMETER Path
NT path to file.
.PARAMETER Win32Path
Specify Path is a Win32 path.
.PARAMETER File
Specify an existing NtFile object.
.INPUTS
None
.OUTPUTS
NtApiDotNet.EaBuffer
#>

function Get-NtFileEa {
    [CmdletBinding(DefaultParameterSetName = "FromPath")]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "FromPath")]
        [string]$Path,
        [Parameter(ParameterSetName = "FromPath")]
        [switch]$Win32Path,
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "FromFile")]
        [NtApiDotNet.NtFile]$File,
        [switch]$AsEntries
    )

    $ea = switch ($PsCmdlet.ParameterSetName) {
        "FromFile" {
            $File.GetEa()
        }
        "FromPath" {
            Use-NtObject($f = Get-NtFile -Path $Path -Win32Path:$Win32Path -Access ReadEa) {
                $f.GetEa()
            }
        }
    }
    if ($AsEntries) {
        $ea.Entries | Write-Output
    } else {
        $ea | Write-Output
    }
}

# Legacy name, remove eventually.
Set-Alias -Name "Get-NtEaBuffer" -Value "Get-NtFileEa"

<#
.SYNOPSIS
Set the EA buffer on a file.
.DESCRIPTION
This cmdlet sets the Extended Attribute buffer on a file by path or a NtFile object.
.PARAMETER Path
NT path to file.
.PARAMETER Win32Path
Specify Path is a Win32 path.
.PARAMETER File
Specify an existing NtFile object.
.PARAMETER EaBuffer
Specify the EA buffer to set.
.INPUTS
None
.OUTPUTS
None
#>

function Set-NtFileEa {
    [CmdletBinding(DefaultParameterSetName = "FromPath")]
    Param(
        [Parameter(Mandatory, Position = 0, ParameterSetName = "FromPath")]
        [Parameter(Mandatory, Position = 0, ParameterSetName = "FromPathAndName")]
        [string]$Path,
        [Parameter(ParameterSetName = "FromPath")]
        [Parameter(ParameterSetName = "FromPathAndName")]
        [switch]$Win32Path,
        [Parameter(Mandatory, Position = 0, ParameterSetName = "FromFile")]
        [Parameter(Mandatory, Position = 0, ParameterSetName = "FromFileAndName")]
        [NtApiDotNet.NtFile]$File,
        [Parameter(Mandatory, Position = 1, ParameterSetName = "FromFile")]
        [Parameter(Mandatory, Position = 1, ParameterSetName = "FromPath")]
        [NtApiDotNet.EaBuffer]$EaBuffer,
        [Parameter(Mandatory, Position = 1, ParameterSetName = "FromPathAndName")]
        [Parameter(Mandatory, Position = 1, ParameterSetName = "FromFileAndName")]
        [string]$Name,
        [Parameter(Mandatory, Position = 2, ParameterSetName = "FromPathAndName")]
        [Parameter(Mandatory, Position = 2, ParameterSetName = "FromFileAndName")]
        [byte[]]$Byte,
        [Parameter(Position = 3, ParameterSetName = "FromPathAndName")]
        [Parameter(Position = 3, ParameterSetName = "FromFileAndName")]
        [NtApiDotNet.EaBufferEntryFlags]$Flags = 0
    )

    if ($PSCmdlet.ParameterSetName -eq "FromPathAndName" -or $PSCmdlet.ParameterSetName -eq "FromFileAndName") {
        $EaBuffer = New-NtEaBuffer
        Add-NtEaBuffer -EaBuffer $EaBuffer -Name $Name -Byte $Byte -Flags $Flags
    }

    if ($PSCmdlet.ParameterSetName -eq "FromPath" -or $PSCmdlet.ParameterSetName -eq "FromPathAndName") {
        Use-NtObject($f = Get-NtFile -Path $Path -Win32Path:$Win32Path -Access WriteEa) {
            $f.SetEa($EaBuffer)
        }
    } elseif ($PSCmdlet.ParameterSetName -eq "FromFile" -or $PSCmdlet.ParameterSetName -eq "FromFileAndName"){
        $File.SetEa($EaBuffer)
    }
}

# Legacy name, remove eventually.
Set-Alias -Name "Set-NtEaBuffer" -Value "Set-NtFileEa"

<#
.SYNOPSIS
Remove an EA buffer on a file.
.DESCRIPTION
This cmdlet removes an Extended Attribute buffer on a file by path or a NtFile object.
.PARAMETER Path
NT path to file.
.PARAMETER Win32Path
Specify Path is a Win32 path.
.PARAMETER Name
Specify the name of the buffer to remove.
.INPUTS
None
.OUTPUTS
None
#>

function Remove-NtFileEa {
    [CmdletBinding(DefaultParameterSetName = "FromPath")]
    Param(
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "FromPath")]
        [string]$Path,
        [Parameter(ParameterSetName = "FromPath")]
        [switch]$Win32Path,
        [Parameter(Mandatory = $true, Position = 0, ParameterSetName = "FromFile")]
        [NtApiDotNet.NtFile]$File,
        [Parameter(Mandatory = $true, Position = 1)]
        [string]$Name
    )

    switch ($PsCmdlet.ParameterSetName) {
        "FromFile" {
            $File.RemoveEa($Name)
        }
        "FromPath" {
            Use-NtObject($f = Get-NtFile -Path $Path -Win32Path:$Win32Path -Access WriteEa) {
                $f.RemoveEa($Name)
            }
        }
    }
}

<#
.SYNOPSIS
Write bytes to a file.
.DESCRIPTION
This cmdlet writes bytes to a file optionally specifying the offset.
.PARAMETER File
Specify the file to write to.
.PARAMETER Bytes
Specify the bytes to write.
.PARAMETER Offset
Specify the offset in the file to write to.
.PARAMETER PassThru
Specify to the return the length written.
.INPUTS
None
.OUTPUTS
int
.EXAMPLE
Write-NtFile -File $f -Bytes @(0, 1, 2, 3)
Write to a file at the current offset.
.EXAMPLE
Write-NtFile -File $f -Bytes @(0, 1, 2, 3) -Offset 1234
Write to a file at offset 1234.
.EXAMPLE
$count = Write-NtFile -File $f -Bytes @(0, 1, 2, 3) -PassThru
Write to a file and return the number of bytes written.
#>

function Write-NtFile {
    [CmdletBinding(DefaultParameterSetName = "NoOffset")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 1)]
        [byte[]]$Bytes,
        [parameter(Position = 2, ParameterSetName="UseOffset")]
        [int64]$Offset,
        [switch]$PassThru
    )
    $result = switch($PSCmdlet.ParameterSetName) {
        "NoOffset" {
            $File.Write($Bytes)
        }
        "UseOffset" {
            $File.Write($Bytes, $Offset)
        }
    }

    if ($PassThru) {
        $result | Write-Output
    }
}

<#
.SYNOPSIS
Read bytes from a file.
.DESCRIPTION
This cmdlet writes byte to a file optionally specifying the offset.
.PARAMETER File
Specify the file to read from.
.PARAMETER Length
Specify the number of bytes to read.
.PARAMETER Offset
Specify the offset in the file to read from.
.INPUTS
None
.OUTPUTS
byte[]
.EXAMPLE
Read-NtFile -File $f -Length 8
Read 8 bytes from a file at the current offset.
.EXAMPLE
Read-NtFile -File $f -Length 8 -Offset 1234
Read 8 bytes from a file at offset 1234.
#>

function Read-NtFile {
    [CmdletBinding(DefaultParameterSetName = "NoOffset")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 1)]
        [int]$Length,
        [parameter(Position = 2, ParameterSetName="UseOffset")]
        [int64]$Offset
    )

    $result = switch($PSCmdlet.ParameterSetName) {
        "NoOffset" {
            $File.Read($Length)
        }
        "UseOffset" {
            $File.Read($Length, $Offset)
        }
    }

    Write-Output $result 
}

<#
.SYNOPSIS
Enumerate file entries for a file directory.
.DESCRIPTION
This cmdlet enumerates directory entries from a file directory.
.PARAMETER File
Specify the file directory to enumerate.
.PARAMETER Pattern
A file pattern to specify the files to enumerate. e.g. *.txt.
.PARAMETER FileType
Specify all files or either files or directories.
.PARAMETER ReparsePoint
Enumerate reparse point information.
.PARAMETER ObjectId
Enumerate object ID information.
.PARAMETER IncludePlaceholder
Include placeholder directories in output.
.PARAMETER FileId
Include file ID in the entries.
.PARAMETER ShortName
Include the short name in the output.
.PARAMETER Path
Path to open the directory first.
.PARAMETER Win32Path
Open a win32 path.
.PARAMETER CaseSensitive
Open the file case sensitively, also does case sensitive pattern matching.
.INPUTS
None
.OUTPUTS
NtApiDotNet.FileDirectoryEntry[]
NtApiDotNet.FileIdDirectoryEntry[]
NtApiDotNet.NtFileReparsePoint[]
NtApiDotNet.NtFileObjectId[]
.EXAMPLE
Get-NtFileItem -File $f
Enumerate all file items.
.EXAMPLE
Get-NtFileItem -Path \??\c:\windows
Enumerate all file items in c:\windows.
.EXAMPLE
Get-NtFileItem -Path c:\windows -Win32Path
Enumerate all file items in c:\windows.
.EXAMPLE
Get-NtFileItem -File $f -Pattern *.txt
Enumerate all files with a TXT extension.
.EXAMPLE
Get-NtFileItem -File $f -FileType FilesOnly
Enumerate only files.
.EXAMPLE
Get-NtFileItem -File $f -FileType DirectoriesOnly
Enumerate only directories.
.EXAMPLE
Get-NtFileItem -File $f -ReparsePoint
Enumerate reparse points.
.EXAMPLE
Get-NtFileItem -File $f -ObjectId
Enumerate object IDs.
.EXAMPLE
Get-NtFileItem -File $f -FileId
Enumerate files with file ID.
.EXAMPLE
Get-NtFileItem -File $f -ShortName
Enumerate files with short name.
#>

function Get-NtFileItem {
    [CmdletBinding(DefaultParameterSetName = "Default")]
    Param(
        [parameter(Mandatory, Position = 0, ParameterSetName="Default")]
        [parameter(Mandatory, Position = 0, ParameterSetName="FromReparsePoint")]
        [parameter(Mandatory, Position = 0, ParameterSetName="FromObjectID")]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 0, ParameterSetName="FromPath")]
        [string]$Path,
        [parameter(ParameterSetName="FromPath")]
        [switch]$Win32Path,
        [parameter(ParameterSetName="Default")]
        [parameter(ParameterSetName="FromPath")]
        [string]$Pattern = "*",
        [parameter(ParameterSetName="Default")]
        [parameter(ParameterSetName="FromPath")]
        [NtApiDotNet.FileTypeMask]$FileType = "All",
        [parameter(ParameterSetName="Default")]
        [parameter(ParameterSetName="FromPath")]
        [switch]$FileId,
        [parameter(ParameterSetName="Default")]
        [parameter(ParameterSetName="FromPath")]
        [switch]$ShortName,
        [parameter(ParameterSetName="Default")]
        [parameter(ParameterSetName="FromPath")]
        [switch]$IncludePlaceholder,
        [parameter(ParameterSetName="FromPath")]
        [switch]$CaseSensitive,
        [parameter(ParameterSetName="FromReparsePoint")]
        [switch]$ReparsePoint,
        [parameter(ParameterSetName="FromObjectID")]
        [switch]$ObjectId
    )

    switch($PSCmdlet.ParameterSetName) {
        "Default" {
            $flags = "Default"
            if ($FileId -and $ShortName) {
                $flags = "FileId, ShortName"
            } elseif($FileId) {
                $flags = "FileId"
            } elseif($ShortName) {
                $flags = "ShortName"
            }

            if ($IncludePlaceholder) {
                $flags += ", Placeholders"
            }
            $File.QueryDirectoryInfo($Pattern, $FileType, $flags) | Write-Output
        }
        "FromPath" {
            $attr = "CaseInsensitive"
            if ($CaseSensitive) {
                $attr = 0
            }
            Use-NtObject($file = Get-NtFile -Path $Path -Win32Path:$Win32Path `
                -DirectoryAccess ListDirectory -ShareMode Read -Options DirectoryFile -AttributeFlags $attr) {
                if ($file -ne $null) {
                    Get-NtFileItem -File $file -Pattern $Pattern -FileType $FileType -FileId:$FileId `
                        -ShortName:$ShortName -IncludePlaceholder:$IncludePlaceholder | Write-Output
                }
            }
        }
        "FromReparsePoint" {
            $File.QueryReparsePoints() | Write-Output
        }
        "FromObjectID" {
            $File.QueryObjectIds() | Write-Output
        }
    }
}

<#
.SYNOPSIS
Get change notification events for a file directory.
.DESCRIPTION
This cmdlet gets change notification envents for a file directory.
.PARAMETER File
Specify the file directory to get change notification events from.
.PARAMETER Filter
Specify what types of events to receive.
.PARAMETER WatchSubtree
Specify to watch all directories in a subtree.
.PARAMETER TimeoutSec
Specify a timeout in seconds to wait if the handle is asynchronous.
.PARAMETER Async
Specify to return an asynchronous task instead of waiting. You can use Wait-AsyncTaskResult
to get the result. The handle must be asynchronous.
.INPUTS
None
.OUTPUTS
NtApiDotNet.DirectoryChangeNotification[]
.EXAMPLE
Get-NtFileChange -File $f
Get all change notifications for the file directory.
.EXAMPLE
Get-NtFileChange -File $f -Filter FileName
Get only filename change notifications for the file directory.
.EXAMPLE
Get-NtFileChange -File $f -WatchSubtree
Get all change notifications for the file directory and its children.
.EXAMPLE
Get-NtFileChange -File $f -TimeoutSec 10
Get all change notifications for the file directory, waiting for 10 seconds for a result.
#>

function Get-NtFileChange {
    [CmdletBinding(DefaultParameterSetName = "Sync")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [NtApiDotNet.DirectoryChangeNotifyFilter]$Filter = "All",
        [switch]$WatchSubtree,
        [parameter(ParameterSetName="Sync")]
        [int]$TimeoutSec = -1,
        [parameter(Mandatory, ParameterSetName="Async")]
        [switch]$Async
    )

    if ($Async) {
        $File.GetChangeNotificationFullAsync($Filter, $WatchSubtree) | Write-Output
    } else {
        $timeout = Get-NtWaitTimeout -Infinite
        if ($TimeoutSec -ge 0) {
            $timeout = Get-NtWaitTimeout -Second $TimeoutSec
        }
        $File.GetChangeNotificationFull($Filter, $WatchSubtree, $timeout) | Write-Output
    }
}

<#
.SYNOPSIS
Lock a file range.
.DESCRIPTION
This cmdlet locks a file range in an open file.
.PARAMETER File
Specify the file directory to lock.
.PARAMETER Offset
The offset into the file to lock.
.PARAMETER Length
The length of the locked region.
.PARAMETER All
Specify to lock the entire file.
.PARAMETER Wait
Specify to wait for the lock to be available otherwise fail immediately.
.PARAMETER Exclusive
Specify to create an exclusive lock.
.PARAMETER PassThru
Specify to return a scoped lock which will unlock when disposed.
.INPUTS
None
.OUTPUTS
NtApiDotNet.Utilities.IO.NtFileScopedLock
.EXAMPLE
Lock-NtFile -File $f -Offset 0 -Length 256
Lock the first 256 bytes.
.EXAMPLE
Lock-NtFile -File $f -Offset 0 -Length 256 -Wait
Lock the first 256 bytes and wait if already locked.
.EXAMPLE
Lock-NtFile -File $f -All
Lock the entire file.
.EXAMPLE
Lock-NtFile -File $f -All -Exclusive
Lock the entire file exclusively.
#>

function Lock-NtFile {
    [CmdletBinding(DefaultParameterSetName = "FromOffset")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 1, ParameterSetName="FromOffset")]
        [int64]$Offset,
        [parameter(Mandatory, Position = 2, ParameterSetName="FromOffset")]
        [int64]$Length,
        [parameter(Mandatory, ParameterSetName="All")]
        [switch]$All,
        [switch]$Wait,
        [switch]$Exclusive,
        [switch]$PassThru
    )

    if ($All) {
        $Offset = 0
        $Length = $File.Length
    }

    if ($PassThru) {
        [NtApiDotNet.Utilities.IO.NtFileScopedLock]::Create($File, $Offset, $Length, !$Wait, $Exclusive) | Write-Output
    } else {
        $File.Lock($Offset, $Length, !$Wait, $Exclusive)
    }
}

<#
.SYNOPSIS
Unlock a file range.
.DESCRIPTION
This cmdlet unlocks a file range in an open file.
.PARAMETER File
Specify the file directory to unlock.
.PARAMETER Offset
The offset into the file to unlock.
.PARAMETER Length
The length of the unlocked region.
.PARAMETER All
Specify to unlock the entire file.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Unlock-NtFile -File $f -Offset 0 -Length 256
Unlock the first 256 bytes.
.EXAMPLE
Unlock-NtFile -File $f -All
Unlock the entire file.
#>

function Unlock-NtFile {
    [CmdletBinding(DefaultParameterSetName = "FromOffset")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, Position = 1, ParameterSetName="FromOffset")]
        [int64]$Offset,
        [parameter(Mandatory, Position = 2, ParameterSetName="FromOffset")]
        [int64]$Length,
        [parameter(Mandatory, ParameterSetName="All")]
        [switch]$All
    )

    if ($All) {
        $Offset = 0
        $Length = $File.Length
    }

    $File.Unlock($Offset, $Length)
}

<#
.SYNOPSIS
Sets the disposition on a file.
.DESCRIPTION
This cmdlet sets the disposition on a file such as deleting the file.
.PARAMETER File
Specify the file to set.
.PARAMETER Delete
Specify to mark the file as delete on close.
.PARAMETER PosixSemantics
Specify to mark the file as delete on close with POSIX semantics.
.PARAMETER Flags
Specify disposition flags.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Set-NtFileDisposition -File $f -Delete
Set the file to delete on close.
.EXAMPLE
Set-NtFileDisposition -File $f -Delete:$false
Clear the file delete on close flag.
.EXAMPLE
Set-NtFileDisposition -File $f -Delete -PosixSemantics
Set the file to delete on close with POSIX semantics.
.EXAMPLE
Set-NtFileDisposition -File $f -Flags Delete, IgnoreReadOnlyAttribute
Set the file delete on close flag and ignore the readonly attribute.
#>

function Set-NtFileDisposition {
    [CmdletBinding(DefaultParameterSetName="FromDelete")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory, ParameterSetName="FromDelete")]
        [switch]$Delete,
        [parameter(ParameterSetName="FromDelete")]
        [switch]$PosixSemantics,
        [parameter(Mandatory, Position = 1, ParameterSetName="FromFlags")]
        [NtApiDotNet.FileDispositionInformationExFlags]$Flags
    )

    switch($PSCmdlet.ParameterSetName) {
        "FromDelete" {
            if ($PosixSemantics -and $Delete) {
                $File.SetDispositionEx("Delete, PosixSemantics")
            } else {
                $File.SetDisposition($Delete)
            }
        }
        "FromFlags" {
            $File.SetDispositionEx($Flags)
        }
    }
}

<#
.SYNOPSIS
Gets whether the file is being deleted.
.DESCRIPTION
This cmdlet gets whether the file is going to be deleted when closed.
.PARAMETER File
Specify the file to query.
.INPUTS
None
.OUTPUTS
bool
.EXAMPLE
Get-NtFileDisposition -File $f
Get the file to delete on close flag.
#>

function Get-NtFileDisposition {
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File
    )
    $File.DeletePending | Write-Output
}

<#
.SYNOPSIS
Generate a 8dot3 name for a full name.
.DESCRIPTION
This cmdlet generates a 8dot3 filename from a full name.
.PARAMETER Name
The name to generate from.
.PARAMETER ExtendedCharacters
Allow extended characters.
.INPUTS
None
.OUTPUTS
string
.EXAMPLE
Get-NtFile8dot3Path -Name 0123456789.config
Generate a 8dot3 name from a full name.
#>

function Get-NtFile8dot3Name {
    Param(
        [parameter(Mandatory = $true, Position = 0)]
        [string]$Name,
        [switch]$ExtendedCharacters
    )
    [NtApiDotNet.NtFileUtils]::Generate8dot3Name($Name, $ExtendedCharacters) | Write-Output
}

<#
.SYNOPSIS
Tests if a driver is in the device stack of a file.
.DESCRIPTION
This cmdlet checks if a driver is in the device stack of a file.
.PARAMETER File
The file to check. Works with files or direct device opens.
.PARAMETER DriverPath
The object manager path to the driver object. e.g. \Device\volume or just volume.
.INPUTS
None
.OUTPUTS
Bool
.EXAMPLE
Test-NtFileDriverPath -File $f -DriverPath "Ntfs"
Tests if the Ntfs driver is in the path.
#>

function Test-NtFileDriverPath {
    Param(
        [parameter(Mandatory, Position = 0)]
        [NtApiDotNet.NtFile]$File,
        [parameter(Mandatory = $true, Position = 1)]
        [string]$DriverPath
    )
    $File.DriverInPath($DriverPath)
}

<#
.SYNOPSIS
Get list of mount points.
.DESCRIPTION
This cmdlet queries the mount point manager for a list of mount points.
.INPUTS
None
.OUTPUTS
NtApiDotNet.IO.MountPointManager.MountPoint[]
.EXAMPLE
Get-NtMountPoint
Get list of mount points.
#>

function Get-NtMountPoint {
    [NtApiDotNet.IO.MountPointManager.MountPointManagerUtils]::QueryMountPoints() | Write-Output
}

<#
.SYNOPSIS
Create a new reparse tag buffer.
.DESCRIPTION
This cmdlet creates a new reparse tag buffer.
.PARAMETER Tag
Specify the reparse tag.
.PARAMETER Guid
Specify the GUID for a generic reparse buffer.
.PARAMETER Data
Specify data for the reparse buffer.
.INPUTS
None
.OUTPUTS
NtApiDotNet.OpaqueReparseBuffer
NtApiDotNet.GenericReparseBuffer
.EXAMPLE
New-NtFileReparseBuffer -Tag AF_UNIX -Data @(1, 2, 3, 4)
Create a new opaque reparse buffer.
.EXAMPLE
New-NtFileReparseBuffer -GenericTag 100 -Data @(1, 2, 3, 4) -Guid '8b049aa1-e380-4808-aeb4-dffd9d01c0de'
Create a new opaque reparse buffer.
#>

function New-NtFileReparseBuffer {
    [CmdletBinding(DefaultParameterSetName = "OpaqueBuffer")]
    Param(
        [parameter(Mandatory, Position = 0, ParameterSetName="OpaqueBuffer")]
        [NtApiDotNet.ReparseTag]$Tag,
        [parameter(Mandatory, Position = 0, ParameterSetName="GenericBuffer")]
        [uint32]$GenericTag,
        [parameter(Mandatory, ParameterSetName="GenericBuffer")]
        [guid]$Guid,
        [parameter(Mandatory, Position = 1, ParameterSetName="OpaqueBuffer")]
        [parameter(Mandatory, Position = 1, ParameterSetName="GenericBuffer")]
        [AllowEmptyCollection()]
        [byte[]]$Data
    )

    switch($PSCmdlet.ParameterSetName) {
        "OpaqueBuffer" {
            [NtApiDotnet.OpaqueReparseBuffer]::new($Tag, $Data) | Write-Output
        }
        "GenericBuffer" {
            [NtApiDotNet.GenericReparseBuffer]::new($GenericTag, $Guid, $Data) | Write-Output
        }
    }
}

<#
.SYNOPSIS
Query the quota on a volume.
.DESCRIPTION
This cmdlet queries the quote entries on a volume.
.PARAMETER Volume
Specify the name of the volume, e.g. C: or \Device\HarddiskVolumeX
.PARAMETER Sid
Specify a list of sids to query.
.INPUTS
None
.OUTPUTS
NtApiDotNet.FileQuotaEntry[]
.EXAMPLE
Get-NtFileQuota -Volume C:
Query the quota for the C: volume.
#>

function Get-NtFileQuota {
    Param(
        [parameter(Mandatory, Position = 0)]
        [string]$Volume,
        [NtApiDotNet.Sid[]]$Sid
    )
    try {
        if (!$Volume.StartsWith("\")) {
            $Volume = "\??\" + $Volume
        }
        Use-NtObject($vol = Get-NtFile -Path $Volume `
            -Access Execute -Share Read, Write) {
            $vol.QueryQuota($Sid) | Write-Output
        }
    } catch {
        Write-Error $_
    }
}

<#
.SYNOPSIS
Sets the quota on a volume.
.DESCRIPTION
This cmdlet sets the quote entries on a volume.
.PARAMETER Volume
Specify the name of the volume, e.g. C: or \Device\HarddiskVolumeX
.PARAMETER Sid
Specify the SID to set.
.PARAMETER Limit
Specify the quota limit.
.PARAMETER Threshold
Specify the quota threshold.
.PARAMETER Quota
Specify a list of quota entries.
.INPUTS
None
.OUTPUTS
None
.EXAMPLE
Set-NtFileQuota -Volume C: -Sid "S-1-1-0" -Limit (10*1024*1024) -Threshold (8*1024*1024)
Set quota for the Everyone group with a limit of 10MiB and threshold of 8MiB.
.EXAMPLE
Set-NtFileQuota -Volume C: -Quota $qs
Set quota for a list of quota entries.
#>

function Set-NtFileQuota {
    [CmdletBinding(DefaultParameterSetName="FromSid")]
    Param(
        [parameter(Mandatory, Position = 0)]
        [string]$Volume,
        [parameter(Mandatory, Position = 1, ParameterSetName="FromSid")]
        [NtApiDotNet.Sid]$Sid,
        [parameter(Mandatory, Position = 2, ParameterSetName="FromSid")]
        [int64]$Limit,
        [parameter(Mandatory, Position = 3, ParameterSetName="FromSid")]
        [int64]$Threshold,
        [parameter(Mandatory, Position = 1, ParameterSetName="FromEntry")]
        [NtApiDotNet.FileQuotaEntry[]]$Quota
    )
    try {
        if (!$Volume.StartsWith("\")) {
            $Volume = "\??\" + $Volume
        }
        Use-NtObject($vol = Get-NtFile -Path $Volume `
            -Access WriteData -Share Read, Write) {
            if ($PSCmdlet.ParameterSetName -eq "FromSid") {
                $vol.SetQuota($Sid, $Threshold, $Limit)
            } else {
                $vol.SetQuota($Quota)
            }
        }
    } catch {
        Write-Error $_
    }
}

<#
.SYNOPSIS
Read the USN journal for a volume.
.DESCRIPTION
This cmdlet reads the USN journal reocrds for a volume.
.PARAMETER Volume
Specify the volume to read from.
.PARAMETER StartUsn
Specify the first USN to read from.
.PARAMETER EndUsn
Specify the last USN to read, exclusive.
.PARAMETER ReasonMask
Specify a mask of reason codes to return.
.PARAMETER Unprivileged
Specify to use unprivileged reading. This doesn't return filenames you don't have access to.
.INPUTS
None
.OUTPUTS
NtApiDotNet.IO.UsnJournal.UsnJournalRecord[]
.EXAMPLE
Read-NtFileUsnJournal -Volume C:
Read the USN journal for the C: volume.
#>

function Read-NtFileUsnJournal {
    Param(
        [parameter(Mandatory, Position = 0)]
        [string]$Volume,
        [uint64]$StartUsn = 0,
        [uint64]$EndUsn = [uint64]::MaxValue,
        [NtApiDotNet.IO.UsnJournal.UsnJournalReasonFlags]$ReasonMask = "All",
        [switch]$Unprivileged
    )
    try {
        if (!$Volume.StartsWith("\")) {
            $Volume = "\??\" + $Volume
        }

        $Access = "ReadData"

        if ($Unprivileged) {
            $Volume += "\"
            $Access = "Synchronize"
        }

        Use-NtObject($vol = Get-NtFile -Path $Volume `
            -Access $Access -Share Read, Write) {
            if ($Unprivileged) {
                [NtApiDotNet.IO.UsnJournal.UsnJournalUtils]::ReadJournalUnprivileged($vol, $StartUsn, $EndUsn, $ReasonMask) | Write-Output
            } else {
                [NtApiDotNet.IO.UsnJournal.UsnJournalUtils]::ReadJournal($vol, $StartUsn, $EndUsn, $ReasonMask) | Write-Output
            }
        }
    } catch {
        Write-Error $_
    }
}

<#
.SYNOPSIS
Gets an IO control code structure.
.DESCRIPTION
This cmdlet gets an IO control code structure from a code or from its constituent parts.
.PARAMETER ControlCode
Specify the control code for the structure.
.PARAMETER DeviceType
Specify the device type component.
.PARAMETER Function
Specify the function code component.
.PARAMETER Method
Specify the control method component.
.PARAMETER Access
Specify the access component.
.PARAMETER LookupName
Specify to try and lookup a known name for the IO control code. If no name found will just return an empty string.
.PARAMETER All
Specify to return all known IO control codes with names.
.PARAMETER Name
Specify to lookup an IO control code with a name.
.PARAMETER AsInt
When looking up by name return the control code as an integer.
.OUTPUTS
NtApiDotNet.NtIoControlCode
System.String
.EXAMPLE
Get-NtIoControlCode 0x110028
Get the IO control code structure for a control code.
.EXAMPLE
Get-NtIoControlCode 0x110028 -LookupName
Get the IO control code structure for a control code and lookup its name (if known).
.EXAMPLE
Get-NtIoControlCode -DeviceType NAMED_PIPE -Function 10 -Method Buffered -Access Any
Get the IO control code structure from component parts.
.EXAMPLE
Get-NtIoControlCode -DeviceType NAMED_PIPE -Function 10 -Method Buffered -Access Any -LookupName
Get the IO control code structure from component parts and lookup its name (if known).
.EXAMPLE
Get-NtIoControlCode -Name "FSCTL_GET_REPARSE_POINT"
Get the IO control code structure from a known name.
.EXAMPLE
Get-NtIoControlCode -Name "FSCTL_GET_REPARSE_POINT" -AsInt
Get the IO control code structure from a known name as output an integer.
#>

function Get-NtIoControlCode {
    [CmdletBinding(DefaultParameterSetName = "FromCode")]
    Param(
        [Parameter(Position = 0, ParameterSetName = "FromCode", Mandatory = $true)]
        [int]$ControlCode,
        [Parameter(ParameterSetName = "FromParts", Mandatory = $true)]
        [NtApiDotNet.FileDeviceType]$DeviceType,
        [Parameter(ParameterSetName = "FromParts", Mandatory = $true)]
        [int]$Function,
        [Parameter(ParameterSetName = "FromParts", Mandatory = $true)]
        [NtApiDotNet.FileControlMethod]$Method,
        [Parameter(ParameterSetName = "FromParts", Mandatory = $true)]
        [NtApiDotNet.FileControlAccess]$Access,
        [Parameter(ParameterSetName = "FromParts")]
        [Parameter(ParameterSetName = "FromCode")]
        [switch]$LookupName,
        [Parameter(ParameterSetName = "FromAll", Mandatory = $true)]
        [switch]$All,
        [Parameter(ParameterSetName = "FromName", Mandatory = $true)]
        [string]$Name,
        [Parameter(ParameterSetName = "FromParts")]
        [Parameter(ParameterSetName = "FromName")]
        [switch]$AsInt
    )
    $result = switch ($PsCmdlet.ParameterSetName) {
        "FromCode" {
            [NtApiDotNet.NtIoControlCode]::new($ControlCode)
        }
        "FromParts" {
            [NtApiDotNet.NtIoControlCode]::new($DeviceType, $Function, $Method, $Access)
        }
        "FromAll" {
            [NtApiDotNet.NtWellKnownIoControlCodes]::GetKnownControlCodes()
        }
        "FromName" {
            [NtApiDotNet.NtWellKnownIoControlCodes]::GetKnownControlCodeByName($Name)
        }
    }

    if ($LookupName) {
        return [NtApiDotNet.NtWellKnownIoControlCodes]::KnownControlCodeToName($result)
    }

    if ($AsInt) {
        $result.ToInt32() | Write-Output
    } else {
        $result | Write-Output
    }
}