Carbon.Accounts/Modules/PureInvoke/1.0.0/PureInvoke.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

using namespace System.ComponentModel
using namespace System.Runtime.InteropServices

#Requires -Version 5.1
Set-StrictMode -Version 'Latest'

# Functions should use $script: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.
$script:moduleRoot = $PSScriptRoot

foreach ($csFile in (Get-ChildItem -Path (Join-Path -Path $script:moduleRoot -ChildPath 'src') -Filter '*.cs'))
{
    Add-Type -TypeDefinition (Get-Content -Raw -Path $csFile.FullName)
}

# 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 $script:moduleRoot -ChildPath 'Functions\*.ps1'
if( (Test-Path -Path $functionsPath) )
{
    foreach( $functionPath in (Get-Item $functionsPath) )
    {
        . $functionPath.FullName
    }
}



function Invoke-AdvapiLookupAccountName
{
    <#
    .SYNOPSIS
    Calls the Advanced Windows 32 Base API (advapi32.dll) `LookupAccountName` function.
 
    .DESCRIPTION
    The `Invoke-AdvapiLookupAccountName` function calls the advapi32.dll API's `LookupAccountName` function, which looks up
    an account name and returns its domain, SID, and use. Pass the account name to the `AccountName` parameter and the
    system name to the `SystemName` parameter, which are passed to `LookupAccountName` as the `lpAccountName` and
    `lpSystemName` arguments, respectively. The function returns an object with properties for each of the
    `LookupAccountName` function's out parameters: `ReferencedDomainName`, `Sid`, and `Use`.
 
    .LINK
    https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountnamea
 
    .EXAMPLE
    Invoke-AdvapiLookupAccountName -AccountName ([Environment]::UserName)
 
    Demonstrates how to call this function by passing a username to the `AccountName` parameter.
    #>

    [CmdletBinding()]
    param(
        # The name of the system.
        [String] $SystemName,

        # The account name to lookup.
        [Parameter(Mandatory)]
        [String] $AccountName
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $result = [pscustomobject]@{
        ReferencedDomainName = '';
        Sid = [byte[]]::New(0);
        Use = [PureInvoke.AdvApi32+SidNameUse]::Unknown
    }

    [byte[]] $sid = [byte[]]::New(0);

    # cb = count of bytes
    [UInt32] $cbSid = 0;
    [Text.StringBuilder] $domainName = [Text.StringBuilder]::New()
    # cch = count of chars
    [UInt32] $cchDomainName = $domainName.Capacity;
    [PureInvoke.AdvApi32+SidNameUse] $sidNameUse = [PureInvoke.AdvApi32+SidNameUse]::Unknown;

    $err = [PureInvoke.WinError]::Ok
    if ([PureInvoke.AdvApi32]::LookupAccountName($SystemName, $AccountName,
                                              $sid, [ref] $cbSid,
                                              $domainName, [ref] $cchDomainName,
                                              [ref]$sidNameUse))
    {
        Write-Win32Error
        return
    }

    $err = [Marshal]::GetLastWin32Error();
    if ($err -eq [PureInvoke.WinError]::InsufficientBuffer -or $err -eq [PureInvoke.WinError]::InvalidFlags)
    {
        $sid = [byte[]]::New($cbSid);
        [void]$domainName.EnsureCapacity([int]$cchDomainName);
        if (-not [PureInvoke.AdvApi32]::LookupAccountName($SystemName, $AccountName, $sid, [ref] $cbSid,
                                                       $domainName, [ref] $cchDomainName,
                                                       [ref] $sidNameUse))
        {
            Write-Win32Error
            return
        }
    }
    else
    {
        Write-Win32Error
        return
    }

    $result.ReferencedDomainName = $domainName.ToString()
    $result.Sid = $sid
    $result.Use = $sidNameUse

    return $result
}


function Invoke-AdvapiLookupAccountSid
{
    <#
    .SYNOPSIS
    Calls the Advanced Windows 32 Base API (advapi32.dll) `LookupAccountSid` function.
 
    .DESCRIPTION
    The `Invoke-AdvapiLookupAccountSid` function calls the advapi32.dll API's `LookupAccountSid` function, which looks up a
    SID and returns its account name, domain name, and use. Pass the SID as a byte array to the `Sid` parameter and the
    system name to the `SystemName` parameter, which are passed to `LookupAccountSid` as the `Sid` and `lpSystemName`
    arguments, respectively. The function returns an object with properties for each of the `LookupAccountSid`
    function's out parameters: `Name`, `ReferencedDomainName`, and `Use`.
 
    .LINK
    https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-lookupaccountsida
 
    .EXAMPLE
    Invoke-AdvapiLookupAccountSid -Sid $sid
 
    Demonstrates how to call this function by passing a sid to the `Sid` parameter.
    #>

    [CmdletBinding()]
    param(
        [String] $SystemName,

        [Parameter(Mandatory)]
        [byte[]] $Sid
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    $result = [pscustomobject]@{
        Name = '';
        ReferencedDomainName = ''
        Use = [PureInvoke.AdvApi32+SidNameUse]::Unknown
    }

    [Text.StringBuilder] $name = [Text.StringBuilder]::New()
    # cch = count of chars
    [UInt32] $cchName = $name.Capacity;

    [Text.StringBuilder] $domainName = [Text.StringBuilder]::New()
    [UInt32] $cchDomainName = $domainName.Capacity;

    [PureInvoke.AdvApi32+SidNameUse] $sidNameUse = [PureInvoke.AdvApi32+SidNameUse]::Unknown;

    $err = [PureInvoke.WinError]::Ok

    if (-not ([PureInvoke.AdvApi32]::LookupAccountSid($SystemName, $sid, $name, [ref] $cchName,
                                                   $domainName, [ref] $cchDomainName, [ref] $sidNameUse)))
    {
        $err = [Marshal]::GetLastWin32Error();
        if ($err -eq [PureInvoke.WinError]::InsufficientBuffer)
        {
            [void]$name.EnsureCapacity([int]$cchName);
            [void]$domainName.EnsureCapacity([int]$cchName);
            $err = 0
            if (-not [PureInvoke.AdvApi32]::LookupAccountSid($SystemName, $sid,  $name, [ref] $cchName,
                                                        $domainName, $cchDomainName, [ref] $sidNameUse))
            {
                $err = [Marshal]::GetLastWin32Error()
            }
        }
    }

    if ($err)
    {
        Write-Win32Error
        return
    }

    $result.ReferencedDomainName = $domainName.ToString()
    $result.Name = $name.ToString()
    $result.Use = $sidNameUse

    return $result
}


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)
        }
    }
}


function Write-Win32Error
{
    [CmdletBinding()]
    param(
        [String] $Message
    )

    Set-StrictMode -Version 'Latest'
    Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState

    if ($Message)
    {
        $Message.TrimEnd('.')
        $Message = "${Message}: "
    }

    $win32Ex = [Win32Exception]::New()
    $msg = "${Message}$($win32Ex.Message) (0x$($win32Ex.ErrorCode.ToString('x'))/$($win32Ex.NativeErrorCode))."
    Write-Error -Message $msg -ErrorAction $ErrorActionPreference
}