Carbon.Security/Carbon.Core/Carbon.Core.psm1
# Copyright Aaron Jensen and 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 # 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 } } function Add-CTypeData { <# .SYNOPSIS Adds type data to a type only if the type data doesn't already exist. .DESCRIPTION The `Add-CTypeData` function uses PowerShell's `Update-TypeData` cmdlet to add type data to a type, but only if the given type data doesn't already exist. Pass the type to the `Type` parameter or the type name to the `TypeName` parameter, the new type data member type to the `MemberType` parameter (e.g. `AliasProperty`, `NoteProperty`, `ScriptProperty`, or `ScriptMethod`), the member name to the `MemberName` parameter, and the member's value/implementation to the `Value` parameter. Note that the `Type` parameter should be the bare name of the type, e.g. `Diagnostics.Process`, *without* square brackets. If the type already has an equivalent member with the name given by the `MemberName` parameter, nothing happens, and the function returns. .EXAMPLE Add-CTypeData -Type Diagnostics.Process -MemberType ScriptProperty -MemberName 'ParentID' -Value $scriptBlock Demonstrates how to create a script property on a type. In this example, the `System.Diagnostics.Process` type will be given a `ParentID` property that runs the code in the script block in the `$scriptBlock` variable. .EXAMPLE Add-CTypeData -Type Diagnostics.Process -MemberType ScriptMethod -MemberName 'GetParentID()' -Value $scriptBlock Demonstrates how to create a script method on a type. In this example, the `System.Diagnostics.Process` type will be given a `GetParentID()` method that runs the code in the script block in the `$scriptBlock` variable. .EXAMPLE Add-CTypeData -Type Diagnostics.Process -MemberType AliasProperty -MemberName 'ProcessId' -Value 'Id' Demonstrates how to create an alias script property on a type. In this example, the `System.Diagnostics.Process` type will be given a `ProcessId` property that is an alias to the 'Id' property. .EXAMPLE Add-CTypeData -Type Diagnostics.Process -MemberType NoteProperty -MemberName 'ParentID' -Value $parentPid Demonstrates how to create a ntoe property on a type. In this example, the `System.Diagnostics.Process` type will be given a `ParentID` property that returns the value in the `$parentPid` variable. #> [CmdletBinding()] param( # The type on which to add the type data. This should be the bare type name, e.g. Diagnostics.Process, *not* # the type surrounded by square brackets, e.g. `[Diagnostics.Process]`. [Parameter(Mandatory, ParameterSetName='ByType')] [Type] $Type, # The name of the type on which to add the type data. [Parameter(Mandatory, ParameterSetName='ByTypeName')] [String] $TypeName, # The member type of the new type data. Only `AliasProperty`, `NoteProperty`, `ScriptProperty`, `ScriptMethod` # are supported. [Parameter(Mandatory)] [ValidateSet('AliasProperty', 'NoteProperty', 'ScriptProperty', 'ScriptMethod')] [Management.Automation.PSMemberTypes] $MemberType, # The type data's member name. [Parameter(Mandatory)] [String] $MemberName, # The value for the member. If `MemberName` is: # # * `AliasProperty`, this should be the name of the target property. # * `NoteProperty`, the literal value of the property. # * `ScriptProperty`, a script block that return's the property value. # * `ScriptMethod`, a script block that implements the method logic. [Parameter(Mandatory)] [Object] $Value ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $memberTypeMsg = '{0,-14}' -f $MemberType if( -not $TypeName ) { $TypeName = $Type.FullName } if( $Type ) { if( $MemberType -like '*Property' ) { if( ($Type.GetProperties() | Where-Object Name -EQ $MemberName) ) { Write-Debug ("Type $($memberTypeMsg) [$($TypeName)] $($MemberName)") return } } elseif( $MemberType -like '*Method') { if( ($Type.GetMethods() | Where-Object Name -EQ $MemberName) ) { Write-Debug ("Type $($memberTypeMsg) [$($TypeName)] $($MemberName)") return } } } $typeData = Get-TypeData -TypeName $TypeName if( $typeData -and $typeData.Members.ContainsKey($MemberName) ) { Write-Debug ("TypeData $($memberTypeMsg) [$($TypeName)] $($MemberName)") return } Write-Debug ("TypeData + $($memberTypeMsg) [$($TypeName)] $($MemberName)") Update-TypeData -TypeName $TypeName -MemberType $MemberType -MemberName $MemberName -Value $Value } function ConvertTo-CBase64 { <# .SYNOPSIS Base64 encodes things. .DESCRIPTION The `ConvertTo-CBase64` function base64 encodes things. Pipe what you want to encode to `ConvertTo-CBase64`. The function can encode: * [String] * [byte] * [char] * Signed integers: [int16], [int], [int64] (i.e. [long]) * Unsigned integers: [uint16], [uint32], [uint64] * Floating point numbers: [float], [double] * [bool] For each item piped to `ConvertTo-CBase64`, the function returns that item base64 encoded. If you pipe all bytes or all chars to `ConvertTo-CBase64`, it will encode all the bytes and chars together. This allows you to do this: [IO.File]::ReadAllBytes('some file') | ConvertTo-CBase64 and get back a single string for all the bytes/chars. By default, `ConvertTo-CBase64` uses Unicode/UTF-16 encoding when converting strings to base64 (this is the default encoding of strings by .NET and PowerShell). To use a different encoding, pass it to the `Encoding` parameter (`[Text.Encoding] | Get-Member -Static` will show all the default encodings). .EXAMPLE 'Encode me, please!' | ConvertTo-CBase64 Demonstrates how to encode a string in base64. .EXAMPLE 'Encode me, please!' | ConvertTo-CBase64 -Encoding ([Text.Encoding]::ASCII) Demonstrates how to use a custom encoding when converting a string to base64. The parenthesis around the encoding is required by the PowerShell language. .EXAMPLE [IO.File]::ReadAllBytes('path to some file') | ConvertTo-CBase64 Demonstrates that you can pipe an array of bytes to `ConvertTo-CBase64` and you'll get back a single string of all the bytes base64 encoded. .EXAMPLE [IO.File]::ReadAllText('path to some file').ToCharArray() | ConvertTo-CBase64 Demonstrates that you can pipe an array of chars to `ConvertTo-CBase64` and you'll get back a single string of all the chars base64 encoded. .EXAMPLE @( $true, [int16]1, [int]2, [long]3, [uint16]4, [uint32]5, [uint64]6, [float]7.8, [double]9.0) | ConvertTo-CBase64 Demonstrates that `ConvertTo-CBase64` can convert booleans, all sizes of signed and unsigned ints, floats, and doubles to base64. #> [CmdletBinding()] [OutputType([String])] param( [Parameter(Mandatory,ValueFromPipeline,Position=0)] [AllowNull()] [AllowEmptyString()] # The value to base64 encode. [Object]$InputObject, # The encoding to use. Default is Unicode/UTF-16 (the default .NET encoding for strings). This parameter is only # used if encoding a string or char array. [Text.Encoding]$Encoding = ([Text.Encoding]::Unicode) ) begin { Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $collector = $null $collectingBytes = $false $collectingChars = $false $collecting = $false $stopProcessing = $false $inspectedFirstItem = $false } process { if( $stopProcessing ) { return } if( $null -eq $InputObject ) { return } if( $InputObject -is [Collections.IEnumerable] -and $InputObject -isnot [String] ) { Write-Debug "$($InputObject.GetType().FullName)" $InputObject | ConvertTo-CBase64 -Encoding $Encoding return } $isByte = $InputObject -is [byte] $isChar = $InputObject -is [char] if( $PSCmdlet.MyInvocation.ExpectingInput -and -not $inspectedFirstItem ) { $inspectedFirstItem = $true if( $isByte ) { $collecting = $true $collectingBytes = $true $collector = [Collections.Generic.List[byte]]::New() Write-Debug -Message ("Collecting bytes.") } elseif( $isChar ) { $collecting = $true $collectingChars = $true $collector = [Collections.Generic.List[char]]::New() Write-Debug -Message ("Collecting chars.") } } if( $collecting ) { # Looks like we didn't get passed an array of bytes or chars, but an array of mixed object types. if( (-not $isByte -and $collectingBytes) -or (-not $isChar -and $collectingChars) ) { $collecting = $false # Since we are no longer collecting, we need to encode all the previous items we collected. foreach( $item in $collector ) { ConvertTo-CBase64 -InputObject $item -Encoding $Encoding } ConvertTo-CBase64 -InputObject $InputObject -Encoding $Encoding return } [void]$collector.Add($InputObject) return } if( $InputObject -is [String] ) { return [Convert]::ToBase64String($Encoding.GetBytes($InputObject)) } if( $isByte ) { return [Convert]::ToBase64String([byte[]]$InputObject) } if( $InputObject -is [bool] -or $isChar -or $InputObject -is [int16] -or $InputObject -is [int] -or ` $InputObject -is [long] -or $InputObject -is [uint16] -or $InputObject -is [uint32] -or ` $InputObject -is [uint64] -or $InputObject -is [float] -or $InputObject -is [double] ) { return [Convert]::ToBase64String([BitConverter]::GetBytes($InputObject)) } $stopProcessing = $true $msg = "Failed to base64 encode ""$($InputObject.GetType().FullName)"" object. The " + 'ConvertTo-CBase64 function can only convert strings, chars, bytes, bools, all signed and unsigned ' + 'integers, floats, and doubles.' Write-Error -Message $msg -ErrorAction $ErrorActionPreference } end { if( $stopProcessing ) { return } if( -not $collecting ) { return } if( $collectingChars ) { $bytes = $Encoding.GetBytes($collector.ToArray()) } elseif( $collectingBytes ) { $bytes = $collector.ToArray() } [Convert]::ToBase64String($bytes) } } function Get-CPathProvider { <# .SYNOPSIS Returns a path's PowerShell provider. .DESCRIPTION When you want to do something with a path that depends on its provider, use this function. The path doesn't have to exist. If you pass in a relative path, it is resolved relative to the current directory. So make sure you're in the right place. .OUTPUTS System.Management.Automation.ProviderInfo. .EXAMPLE Get-CPathProvider -Path 'C:\Windows' Demonstrates how to get the path provider for an NTFS path. #> [CmdletBinding()] param( # The path whose provider to get. [Parameter(Mandatory)] [String] $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $driveName = $Path | Split-Path -Qualifier -ErrorAction Ignore if (-not $driveName) { $driveName = Get-Location | Split-Path -Qualifier -ErrorAction Ignore if (-not $driveName) { $driveName = (Get-Location).Path.Substring(0, 1) } } $driveName = $driveName.TrimEnd(':') $drive = Get-PSDrive -Name $driveName -ErrorAction Ignore if( -not $drive ) { $drive = Get-PSDrive -PSProvider $driveName -ErrorAction Ignore } if (-not $drive) { $msg = "Failed to get provider for path ${Path} because there is no drive named ""${driveName}"" and no " + "that uses provider ""${driveName}""." Write-Error -Message $msg -ErrorAction $ErrorActionPreference return } $drive | Select-Object -First 1 | Select-Object -ExpandProperty 'Provider' } function Get-CPowershellPath { <# .SYNOPSIS Gets the path to powershell.exe. .DESCRIPTION Returns the path to the powershell.exe binary for the machine's default architecture (i.e. x86 or x64). If you're on a x64 machine and want to get the path to x86 PowerShell, set the `x86` switch. Here are the possible combinations of operating system, PowerShell, and desired path architectures, and the path they map to. +-----+-----+------+--------------------------------------------------------------+ | OS | PS | Path | Result | +-----+-----+------+--------------------------------------------------------------+ | x64 | x64 | x64 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | | x64 | x64 | x86 | $env:windir\SysWOW64\Windows PowerShell\v1.0\powershell.exe | | x64 | x86 | x64 | $env:windir\sysnative\Windows PowerShell\v1.0\powershell.exe | | x64 | x86 | x86 | $env:windir\SysWOW64\Windows PowerShell\v1.0\powershell.exe | | x86 | x86 | x64 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | | x86 | x86 | x86 | $env:windir\System32\Windows PowerShell\v1.0\powershell.exe | +-----+-----+------+--------------------------------------------------------------+ .EXAMPLE Get-CPowerShellPath Returns the path to the version of PowerShell that matches the computer's architecture (i.e. x86 or x64). .EXAMPLE Get-CPowerShellPath -x86 Returns the path to the x86 version of PowerShell. Only valid on Windows. #> [CmdletBinding()] param( # The architecture of the PowerShell executable to run. The default is the architecture of the current # process. [switch]$x86 ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState Write-Debug "[Carbon\Get-CPowerShellPath]" # Map the system directory name from the current PowerShell architecture to the requested architecture. $sysDirNames = @{ # If PowerShell is 64-bit 'x64' = @{ # These are paths to PowerShell matching requested architecture. 'x64' = 'System32'; 'x86' = 'SysWOW64'; }; # If PowerShell is 32-bit. 'x86' = @{ # These are the paths to get to the appropriate architecture. 'x64' = 'sysnative'; 'x86' = 'System32'; } } $executableName = 'powershell.exe' $edition = 'Desktop' if( (Test-CPowerShell -IsCore) ) { $edition = 'Core' $executableName = 'pwsh' if( (Test-COperatingSystem -IsWindows) ) { $executableName = "$($executableName).exe" } } Write-Debug -Message " Edition $($edition)" # PowerShell is always in the same place on x86 Windows. $osArchitecture = 'x64' if( (Test-COperatingSystem -Is32Bit) ) { $osArchitecture = 'x32' return Join-Path -Path $PSHOME -ChildPath $executableName } Write-Debug -Message " Operating System Architecture $($osArchitecture)" $architecture = 'x64' if( $x86 ) { $architecture = 'x86' } $psArchitecture = 'x64' if( (Test-CPowerShell -Is32Bit) ) { $psArchitecture = 'x86' } Write-Debug -Message " PowerShell Architecture $($psArchitecture)" Write-Debug -Message " Requested Architecture $($architecture)" $sysDirName = $sysDirNames[$psArchitecture][$architecture] Write-Debug -Message " Architecture SysDirName $($sysDirName)" $path = $PSHOME -replace '\b(System32|SysWOW64)\b', $sysDirName return Join-Path -Path $path -ChildPath $executableName } function Invoke-CPowerShell { <# .SYNOPSIS Invokes a new `powershell.exe` process. .DESCRIPTION The `Invoke-CPowerShell` scripts executes a new PowerShell process. Pass the parameters to pass to the executable to the ArgumentList parameter. The function uses the `&` operator to run PowerShell. By default, the PowerShell executable in `$PSHOME` is used. In Windows PowerShell (i.e. powershell.exe), the PowerShell executable in "$PSHOME" that matches the architecture of the operating system. Use the `x86` switch to use 32-bit `powershell.exe`. Because this function uses the `&` operator to execute PowerShell, all the PowerShell streams from the invoked command are returned (e.g. stdout, verbose, warning, error, stderr, etc.). To use a different PowerShell executable, like PowerShell Core (i.e. pwsh), pass the path to the PowerShell executable to the `Path` parameter. If the PowerShell executable is in your PATH, you can pass just the executable name. If you want to run an encoded command, pass it to the `Command` parameter. The value of the Command parameter will be base64 encoded and added to the end of the arguments in the ArgumentList parameter, along with the "-EncodedCommand" switch. You can run the PowerShell process as a different user by passing that user's credentials to the `Credential` parameter. `Invoke-CPowerShell` uses the Start-Job cmdlet to start a background job with those credentials. `Start-Job` runs PowerShell with the `&` operator. There is a known issue on Linux and macOS that prevents the `Start-Job` cmdlet (what `Invoke-CPowerShell` uses to run PowerShell as another user) from starting PowerShell as another user. See https://github.com/PowerShell/PowerShell/issues/7172 for more information. .EXAMPLE Invoke-CPowerShell -ArgumentList '-NoProfile','-NonInteractive','-Command','$PID' Demonstrates how to start a new PowerShell process. .EXAMPLE Invoke-CPowerShell -Command $aLargePSScript -ArgumentList '-NoProfile','-NonInteractive' Demonstrates how to run an encoded command. In this example, `Invoke-CPowerShell` encodes the command in the `Command` parameter, then runs PowerShell with `-NoProfile -NonInteractive -EncodedCommand $encodedCommand` parameters. .EXAMPLE Invoke-CPowerShell -Credential $cred -ArgumentList '-NoProfile','-NonInteractive','-Command','[Environment]::UserName' Demonstrates how to run PowerShell as a different user by passing that user's credentials to the `Credential` parameter. This credential is passed to the `Start-Job` cmdlet's `Credential` parameter, then PowerShell is executed using the `&` operator. .EXAMPLE Invoke-CPowerShell -x86 -ArgumentList '-Command','[Environment]::Is64BitProcess' Demonstrates how to run PowerShell in a 32-bit process. This switch only has an effect on 64-bit Windows operating systems. On other systems, use the `-Path` parameter to run PowerShell with a different architecture (which must be installed). .EXAMPLE Invoke-CPowerShell -Path 'pwsh' -ArgumentList '-Command','$PSVersionTable.Edition' Demonstrates how to use a custom PowerShell executable. In this case the first `pwsh` command found in your PATH environment variable is used. #> [CmdletBinding()] param( [Parameter(Mandatory)] # Any arguments to pass to PowerShell. They are passed as-is, so you'll need to handle any necessary # escaping. # # If you need to run an encoded command, use the `Command` parameter to pass the command and this parameter to # pass other parameters. The encoded command will be added to the end of the arguments. [Object[]]$ArgumentList, # The command to run, as a string. The command will be base64 encoded first and passed to PowerShell's # `EncodedCommand` parameter. [String]$Command, # Run PowerShell as a specific user. Pass that user's credentials. # # There is a known issue on Linux and macOS that prevents the `Start-Job` cmdlet (what `Invoke-CPowerShell` # uses to run PowerShell as another user) from starting PowerShell as another user. See # https://github.com/PowerShell/PowerShell/issues/7172 for more information. [pscredential]$Credential, # Run the x86 (32-bit) version of PowerShell. If not provided, the version which matches the OS architecture # is used, *regardless of the architecture of the currently running process*. I.e. this command is run under # a 32-bit PowerShell on a 64-bit operating system, without this switch, `Invoke-CPowerShell` will start a # 64-bit "PowerShell". # # This switch is only used on Windows. [switch]$x86, # The path to the PowerShell executable to use. The default is to use the executable in "$PSHOME". On Windows, # the PowerShell executable in the "$PSHOME" that matches the operating system's architecture is used. # # If the PowerShell executable is in your `PATH`, you can pass the executable name instead. [String]$Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if( -not $Path ) { $params = @{ } if( $x86 ) { $params.x86 = $true } $Path = Get-CPowerShellPath @params } $ArgumentList = & { if( $ArgumentList ) { $ArgumentList | Write-Output } if( $Command ) { '-EncodedCommand' | Write-Output $Command | ConvertTo-CBase64 | Write-Output } } Write-Verbose -Message $Path $ArgumentList | ForEach-Object { Write-Verbose -Message " $($_)" } if( $Credential ) { $location = Get-Location $currentDir = [Environment]::CurrentDirectory $output = $null $WhatIfPreference = $false Start-Job -Credential $Credential -ScriptBlock { Set-Location $using:location [Environment]::CurrentDirectory = $using:currentDir & $using:Path $using:ArgumentList $LASTEXITCODE exit $LASTEXITCODE } | Receive-Job -Wait -AutoRemoveJob | Tee-Object 'output' | Select-Object -SkipLast 1 $LASTEXITCODE = $output | Select-Object -Last 1 } else { & $Path $ArgumentList } Write-Verbose -Message " LASTEXITCODE $($LASTEXITCODE)" } function Resolve-CFullPath { <# .SYNOPSIS Converts a relative path to an absolute path. .DESCRIPTION Unlike `Resolve-Path`, this function does not check whether the path exists. It just converts relative paths to absolute paths using .NET's `[IO.Path]::GetFullPath()` method. Unrooted paths (e.g. `..\..\See\I\Do\Not\Have\A\Root`) are first joined with the current directory (as returned by `Get-Location`). .EXAMPLE Resolve-CFullPath -Path 'C:\Projects\Carbon\Test\..\Carbon\FileSystem.ps1' Returns `C:\Projects\Carbon\Carbon\FileSystem.ps1`. .EXAMPLE Resolve-CFullPath -Path 'C:\Projects\Carbon\..\I\Do\Not\Exist' Returns `C:\Projects\I\Do\Not\Exist`. .EXAMPLE Resolve-CFullPath -Path ..\..\Foo\..\Bar Because the `Path` isn't rooted, joins `Path` with the current directory (as returned by `Get-Location`), and returns the full path. If the current directory is `C:\Projects\Carbon`, returns `C:\Bar`. #> [CmdletBinding()] param( # The path to resolve. [Parameter(Mandatory)] [String] $Path ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if (-not ([System.IO.Path]::IsPathRooted($Path))) { $Path = Join-Path -Path (Get-Location) -ChildPath $Path } return [IO.Path]::GetFullPath($Path) } function Test-COperatingSystem { <# .SYNOPSIS Tests attributes of the current operating system. .DESCRIPTION The `Test-COperatingSystem` function tests atrributes of the current operating system, returning `$true` if they are `$true` and `$false` otherwise. It supports the following switches (only one can be given at at time) that return the following attributes: * `Is32Bit`: is the architecture 32-bit? Uses `[Environment]::Is64BitOperatingSystem`. * `Is64Bit`: is the architecture 64-bit? Uses `[Environment]::Is64BitOperatingSystem`. * `IsWindows`: is the operating system Windows? Uses the `$IsWindows` built-in variable, it it exists. If it doesn't, returns `$true` (only Windows operating systems don't have this variable). * `IsLinux`: is the operating system Linux? Uses the `$IsLinux` built-in variable, if it exists. If it doesn't, returns `$false` (all Linux systems have the `IsLinux` variable). * `IsMacOS`: is the operating system macOS? Uses the `$IsMacOS` built-in variable, if it exists. If it doesn't, returns `$false` (all macOS systems have the `IsMacOS` variable). .OUTPUTS System.Boolean. .LINK http://msdn.microsoft.com/en-us/library/system.environment.is64bitoperatingsystem.aspx .EXAMPLE Test-COperatingSystem -Is32Bit Demonstrates how to test if the current operating system is 32-bit/x86. .EXAMPLE Test-COperatingSystem -Is64Bit Demonstrates how to test if the current operating system is 64-bit/x64. .EXAMPLE Test-COperatingSystem -IsWindows Demonstrates how to test if the current operating system is Windows. .EXAMPLE Test-COperatingSystem -IsLinux Demonstrates how to test if the current operating system is Linux. .EXAMPLE Test-COperatingSystem -IsMacOS Demonstrates how to test if the current operating system is macOS. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory,ParameterSetName='Is32Bit')] [switch]$Is32Bit, [Parameter(Mandatory,ParameterSetName='Is64Bit')] [switch]$Is64Bit, [Parameter(Mandatory,ParameterSetName='IsWindows')] [Alias('IsWindows')] [switch]$Windows, [Parameter(Mandatory,ParameterSetName='IsLinux')] [Alias('IsLinux')] [switch]$Linux, [Parameter(Mandatory,ParameterSetName='IsMacOS')] [Alias('IsMacOS')] [switch]$MacOS ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState switch( $PSCmdlet.ParameterSetName ) { 'Is32Bit' { return -not [Environment]::Is64BitOperatingSystem } 'Is64Bit' { return [Environment]::Is64BitOperatingSystem } 'IsWindows' { if( (Test-Path -Path 'variable:IsWindows') ) { return $IsWindows } return $true } 'IsLinux' { return (Test-Path -Path 'variable:IsLinux') -and $IsLinux } 'IsMacOS' { return (Test-Path -Path 'variable:IsMacOS') -and $IsMacOS } } } function Test-CPowerShell { <# .SYNOPSIS Tests attributes of the current PowerShell process. .DESCRIPTION The `Test-CPowerShell` function tests attributes of the current PowerShell process (or process hosting the current PowerShell runspace). It uses the following switches to test the following conditions: * `Is32Bit`: if the process architecture is 32-bit/x86 (uses `[Environment]::Is64BitProcess`). * `Is64Bit`: if the process architecture is 64-bit/x64 (uses `[Environment]::Is64BitProcess`). * `IsDesktop`: if the process is running on Windows PowerShell (uses `$PSVersionTable.Edition`; if this property doesn't exist, always returns `$true`). * `IsCore`: if the process is running PowerShell Core (uses `$PSVersionTable.Edition`). .OUTPUTS System.Boolean. .EXAMPLE Test-CPowerShell -Is32Bit Demonstrates how to test if the current PowerShell process architecture is 32-bit/x86. .EXAMPLE Test-CPowerShell -Is64Bit Demonstrates how to test if the current PowerShell process architecture is 64-bit/x64. .EXAMPLE Test-CPowerShell -IsDesktop Demonstrates how to test if the current PowerShell process is Windows PowerShell. .EXAMPLE Test-CPowerShell -IsCore Demonstrates how to test if the current PowerShell process is Windows Core. #> [CmdletBinding()] [OutputType([bool])] param( [Parameter(Mandatory,ParameterSetName='Is32Bit')] [switch]$Is32Bit, [Parameter(Mandatory,ParameterSetName='Is64Bit')] [switch]$Is64Bit, [Parameter(Mandatory,ParameterSetName='IsDesktop')] [switch]$IsDesktop, [Parameter(Mandatory,ParameterSetName='IsCore')] [switch]$IsCore ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState switch( $PSCmdlet.ParameterSetName ) { 'Is32Bit' { return -not [Environment]::Is64BitProcess } 'Is64Bit' { return [Environment]::Is64BitProcess } 'IsDesktop' { return -not $PSVersionTable['PSEdition'] -or $PSVersionTable['PSEdition'] -eq 'Desktop' } 'IsCore' { return $PSVersionTable['PSEdition'] -eq 'Core' } } } function Test-CType { <# .SYNOPSIS Tests if a .NET type exists. .DESCRIPTION The `Test-CType` function tests if a .NET type exists. Pass the namespace-qualified type name to the `Name` parameter. For the quickest and most unambiguous type resolution, pass the [assembly-qualifed type name](https://learn.microsoft.com/en-us/dotnet/api/system.type.assemblyqualifiedname#system-type-assemblyqualifiedname) to the `Name` parameter. Otherwise, the function checks each loaded assembly for the type. If the named type is found, returns `$true`. Otherwise, returns `$false`. It stops searching as soon as it finds a type. By default, searches are case-insensitive. To perform a case-sensitive search, use the `CaseSenstive` switch. By default, returns `$true` if at least one type exists. Otherwise, returns `$false`. Use the `$PassThru` switch to get `[Type]` objects returned for all types whose names match. .EXAMPLE Test-CType -Name 'System.String' Demonstrates that you must call `Test-CType` with at least the namespace-qualified type name, not just the type's base name. .EXAMPLE Test-CType -Name 'Carbon.Core.ExampleType' -CaseSensitive Demonstrates how to perform a case-sensitive search. .EXAMPLE Test-CType -Name 'Carbon.Core.DuplicateType' -PassThru Demonstrates how to get `[Type]` object returned for all loaded types that match the passed in type name. #> [CmdletBinding()] param( # The namespace-qualified type name of the type whose existence to test. Assmbly-qualified type names are also # supported and result in the fastest and least ambiguous lookup and results. [Parameter(Mandatory, Position=0)] [String] $Name, # By default, searches are case-insensitive. Use this switch to make the search case-sensitive. [switch] $CaseSensitive, # By default, the function returns `$true` if at least one type exists whose name equals the name passed in. Use # this switch to instead get `[Type]` objects returns for all types whose name is equal to the value of the # `Name` parameter. [switch] $PassThru ) Set-STrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $ignoreCase = -not $CaseSensitive $type = [Type]::GetType($Name, $false, $ignoreCase) if ($type) { if ($PassThru) { $found = $true $type | Write-Output } else { return $true } } foreach ($assembly in [AppDomain]::CurrentDomain.GetAssemblies()) { $type = $assembly.GetType($Name, $false, $ignoreCase) if (-not $type) { continue } if ($PassThru) { $found = $true $type | Write-Output continue } return $true } if ($PassThru) { return } return $false } function Test-CTypeDataMember { <# .SYNOPSIS Tests if a type has an extended type member defined. .DESCRIPTION `Test-CTypeDataMember` tests if a type has an extended type member defined. If the type isn't found, you'll get an error. Returns `$true` if the type is found and the member is defined. Otherwise, returns `$false`. .EXAMPLE Test-CTypeDataMember -TypeName 'Microsoft.Web.Administration.Site' -MemberName 'PhysicalPath' Tests if the `Microsoft.Web.Administration.Site` type has a `PhysicalPath` extended type member defined. #> [CmdletBinding()] [OutputType([bool])] param( # The type name to check. [Parameter(Mandatory)] [String] $TypeName, # The name of the member to check. [Parameter(Mandatory)] [String] $MemberName ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $typeData = Get-TypeData -TypeName $TypeName if( -not $typeData ) { # The type isn't defined or there is no extended type data on it. return $false } return $typeData.Members.ContainsKey( $MemberName ) } # Copyright 2012 Aaron Jensen # # 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. 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 function/cmdlet call in your function. 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 = $true)] #[Management.Automation.PSScriptCmdlet] # The module function's `$PSCmdlet` object. Requires the function be decorated with the `[CmdletBinding()]` attribute. $Cmdlet, [Parameter(Mandatory = $true)] [Management.Automation.SessionState] # 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. $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) } } } |