Carbon.FileSystem.psm1
# Copyright WebMD Health Services # # 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 #Requires -Version 5.1 Set-StrictMode -Version 'Latest' # Functions should use $moduleRoot as the relative root from which to find # things. A published module has its function appended to this file, while a # module in development has its functions in the Functions directory. $moduleRoot = $PSScriptRoot Add-Type @' using System; using System.Text; using System.Collections.Generic; using System.Runtime.InteropServices; namespace Carbon.FileSystem { public static class Kernel32 { #region WinAPI P/Invoke declarations [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern IntPtr FindFirstFileNameW(string lpFileName, uint dwFlags, ref uint StringLength, StringBuilder LinkName); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool FindNextFileNameW(IntPtr hFindStream, ref uint StringLength, StringBuilder LinkName); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool FindClose(IntPtr hFindFile); [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool GetVolumePathName(string lpszFileName, [Out] StringBuilder lpszVolumePathName, uint cchBufferLength); public static readonly IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1); // 0xffffffff; public const int MAX_PATH = 65535; // Max. NTFS path length. #endregion } } '@ # Store each of your module's functions in its own file in the Functions # directory. On the build server, your module's functions will be appended to # this file, so only dot-source files that exist on the file system. This allows # developers to work on a module without having to build it first. Grab all the # functions that are in their own files. $functionsPath = Join-Path -Path $moduleRoot -ChildPath 'Functions\*.ps1' if( (Test-Path -Path $functionsPath) ) { foreach( $functionPath in (Get-Item $functionsPath) ) { . $functionPath.FullName } } <# .SYNOPSIS Retrieves hard link targets from a file. .DESCRIPTION Get-FileHardLink retrieves hard link targets from a file given a file path. This fixes compatibility issues between Windows PowerShell and PowerShell Core when retrieving targets from a hard link. .EXAMPLE Get-FileHardLink -Path $Path Demonstrates how to retrieve a hard link given a file path. #> function Get-FileHardLink { param( # The path whose hard links to get/return. Must exist. [Parameter(Mandatory)] [String] $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState if( -not (Resolve-Path -LiteralPath $Path) ) { return } try { $sbPath = [Text.StringBuilder]::New([Carbon.FileSystem.Kernel32]::MAX_PATH) $charCount = [uint32]$sbPath.Capacity; # in/out character-count variable for the WinAPI calls. # Get the volume (drive) part of the target file's full path (e.g., @"C:\") [void][Carbon.FileSystem.Kernel32]::GetVolumePathName($Path, $sbPath, $charCount) $volume = $sbPath.ToString(); # Trim the trailing "\" from the volume path, to enable simple concatenation # with the volume-relative paths returned by the FindFirstFileNameW() and FindFirstFileNameW() functions, # which have a leading "\" $volume = $volume.Substring(0, $volume.Length - 1); # Loop over and collect all hard links as their full paths. [IntPtr]$findHandle = [IntPtr]::Zero $findHandle = [Carbon.FileSystem.Kernel32]::FindFirstFileNameW($Path, 0, [ref]$charCount, $sbPath) if( [Carbon.FileSystem.Kernel32]::INVALID_HANDLE_VALUE -eq $findHandle) { $errorCode = [Runtime.InteropServices.Marshal]::GetLastWin32Error() $msg = "Failed to find hard links to path ""$($Path | Split-Path -Relative)"": the system error code is ""$($errorCode)""." Write-Error $msg -ErrorAction $ErrorActionPreference return } do { Join-Path -Path $volume -ChildPath $sbPath.ToString() | Write-Output # Add the full path to the result list. $charCount = [uint32]$sbPath.Capacity; # Prepare for the next FindNextFileNameW() call. } while( [Carbon.FileSystem.Kernel32]::FindNextFileNameW($findHandle, [ref]$charCount, $sbPath) ) [void][Carbon.FileSystem.Kernel32]::FindClose($findHandle); } catch { Write-Error -Message $_ -ErrorAction $ErrorActionPreference } } function Use-CallerPreference { <# .SYNOPSIS Sets the PowerShell preference variables in a module's function based on the callers preferences. .DESCRIPTION Script module functions do not automatically inherit their caller's variables, including preferences set by common parameters. This means if you call a script with switches like `-Verbose` or `-WhatIf`, those that parameter don't get passed into any function that belongs to a module. When used in a module function, `Use-CallerPreference` will grab the value of these common parameters used by the function's caller: * ErrorAction * Debug * Confirm * InformationAction * Verbose * WarningAction * WhatIf This function should be used in a module's function to grab the caller's preference variables so the caller doesn't have to explicitly pass common parameters to the module function. This function is adapted from the [`Get-CallerPreference` function written by David Wyatt](https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d). There is currently a [bug in PowerShell](https://connect.microsoft.com/PowerShell/Feedback/Details/763621) that causes an error when `ErrorAction` is implicitly set to `Ignore`. If you use this function, you'll need to add explicit `-ErrorAction $ErrorActionPreference` to every `Write-Error` call. Please vote up this issue so it can get fixed. .LINK about_Preference_Variables .LINK about_CommonParameters .LINK https://gallery.technet.microsoft.com/scriptcenter/Inherit-Preference-82343b9d .LINK http://powershell.org/wp/2014/01/13/getting-your-script-module-functions-to-inherit-preference-variables-from-the-caller/ .EXAMPLE Use-CallerPreference -Cmdlet $PSCmdlet -SessionState $ExecutionContext.SessionState Demonstrates how to set the caller's common parameter preference variables in a module function. #> [CmdletBinding()] param ( [Parameter(Mandatory)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` # attribute. $Cmdlet, [Parameter(Mandatory)] # The module function's `$ExecutionContext.SessionState` object. Requires the function be decorated with the # `[CmdletBinding()]` attribute. # # Used to set variables in its callers' scope, even if that caller is in a different script module. [Management.Automation.SessionState]$SessionState ) Set-StrictMode -Version 'Latest' # List of preference variables taken from the about_Preference_Variables and their common parameter name (taken # from about_CommonParameters). $commonPreferences = @{ 'ErrorActionPreference' = 'ErrorAction'; 'DebugPreference' = 'Debug'; 'ConfirmPreference' = 'Confirm'; 'InformationPreference' = 'InformationAction'; 'VerbosePreference' = 'Verbose'; 'WarningPreference' = 'WarningAction'; 'WhatIfPreference' = 'WhatIf'; } foreach( $prefName in $commonPreferences.Keys ) { $parameterName = $commonPreferences[$prefName] # Don't do anything if the parameter was passed in. if( $Cmdlet.MyInvocation.BoundParameters.ContainsKey($parameterName) ) { continue } $variable = $Cmdlet.SessionState.PSVariable.Get($prefName) # Don't do anything if caller didn't use a common parameter. if( -not $variable ) { continue } if( $SessionState -eq $ExecutionContext.SessionState ) { Set-Variable -Scope 1 -Name $variable.Name -Value $variable.Value -Force -Confirm:$false -WhatIf:$false } else { $SessionState.PSVariable.Set($variable.Name, $variable.Value) } } } |