Carbon.ScheduledTasks/Modules/Carbon.Accounts/1.0.0/Carbon.Accounts.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 using namespace System.Security.Principal #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 $modulesroot = Join-Path -Path $script:moduleRoot -ChildPath 'Modules' -Resolve Import-Module -Name (Join-Path -Path $modulesRoot -ChildPath 'PureInvoke' -Resolve) ` -Function @('Invoke-AdvapiLookupAccountName', 'Invoke-AdvapiLookupAccountSid') ` -Verbose:$false enum Carbon_Accounts_Identity_Type { User = 1 Group Domain Alias WellKnownGroup DeletedAccount Invalid Unknown Computer Label } class Carbon_Accounts_Identity { Carbon_Accounts_Identity([String] $Domain, [String] $Name, [SecurityIdentifier]$Sid, [Carbon_Accounts_Identity_Type]$Type) { $this.Domain = $Domain; $this.Name = $Name; $this.Sid = $Sid; $this.Type = $Type; $this.FullName = $Name if ($Domain) { $this.FullName = "${Domain}\${Name}" } } [String] $Domain [String] $FullName [String] $Name [SecurityIdentifier] $Sid [Carbon_Accounts_Identity_Type] $Type [bool] Equals([Object] $obj) { if ($null -eq $obj -or $obj -isnot [Carbon_Accounts_Identity]) { return $false; } return $this.Sid.Equals($obj.Sid); } [String] ToString() { return $this.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 ConvertTo-CSecurityIdentifier { <# .SYNOPSIS Converts a string or byte array security identifier into a `System.Security.Principal.SecurityIdentifier` object. .DESCRIPTION `ConvertTo-CSecurityIdentifier` converts a SID in SDDL form (as a string), in binary form (as a byte array) into a `System.Security.Principal.SecurityIdentifier` object. It also accepts `System.Security.Principal.SecurityIdentifier` objects, and returns them back to you. If the string or byte array don't represent a SID, an error is written and nothing is returned. .LINK Resolve-CIdentity .LINK Resolve-CIdentityName .EXAMPLE ConvertTo-CSecurityIdentifier -SID 'S-1-5-21-2678556459-1010642102-471947008-1017' Demonstrates how to convert a a SID in SDDL into a `System.Security.Principal.SecurityIdentifier` object. .EXAMPLE ConvertTo-CSecurityIdentifier -SID (New-Object 'Security.Principal.SecurityIdentifier' 'S-1-5-21-2678556459-1010642102-471947008-1017') Demonstrates that you can pass a `SecurityIdentifier` object as the value of the SID parameter. The SID you passed in will be returned to you unchanged. .EXAMPLE ConvertTo-CSecurityIdentifier -SID $sidBytes Demonstrates that you can use a byte array that represents a SID as the value of the `SID` parameter. #> [CmdletBinding()] param( # The SID to convert to a `System.Security.Principal.SecurityIdentifier`. Accepts a SID in SDDL form as a # `string`, a `System.Security.Principal.SecurityIdentifier` object, or a SID in binary form as an array of # bytes. [Parameter(Mandatory)] [Object] $SID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState try { if ( $SID -is [string]) { New-Object 'Security.Principal.SecurityIdentifier' $SID } elseif ($SID -is [byte[]]) { New-Object 'Security.Principal.SecurityIdentifier' $SID,0 } elseif ($SID -is [Security.Principal.SecurityIdentifier]) { $SID } else { $msg = "Invalid SID parameter value [$($SID.GetType().FullName)]${SID}. Only " + '[System.Security.Principal.SecurityIdentifier] objects, SIDs in SDDL form as a [String], or SIDs ' + 'in binary form as a byte array are allowed.' return } } catch { $sidDisplayMsg = '' if ($SID -is [String]) { $sidDisplayMsg = " ""${SID}""" } elseif ($SID -is [byte[]]) { $sidDisplayMsg = " [$($SID -join ', ')]" } $msg = "Exception converting SID${sidDisplayMsg} to a [System.Security.Principal.SecurityIdentifier] " + 'object. This usually means you passed an invalid SID in SDDL form (as a string) or an invalid SID ' + "in binary form (as a byte array): ${_}" Write-Error $msg -ErrorAction $ErrorActionPreference return } } function Resolve-CIdentity { <# .SYNOPSIS Gets domain, name, type, and SID information about a user or group. .DESCRIPTION The `Resolve-CIdentity` function takes an identity name or security identifier (SID) and gets its canonical representation. It returns a `Carbon_Accounts_Identity` object, which contains the following information about the identity: * Domain - the domain the user was found in * FullName - the users full name, e.g. Domain\Name * Name - the user's username or the group's name * Type - the Sid type. * Sid - the account's security identifier as a `System.Security.Principal.SecurityIdentifier` object. The common name for an account is not always the canonical name used by the operating system. For example, the local Administrators group is actually called BUILTIN\Administrators. This function uses the `LookupAccountName` and `LookupAccountSid` Windows functions to resolve an account name or security identifier into its domain, name, full name, SID, and SID type. You may pass a `System.Security.Principal.SecurityIdentifer`, a SID in SDDL form (as a string), or a SID in binary form (a byte array) as the value to the `SID` parameter. You'll get an error and nothing returned if the SDDL or byte array SID are invalid. If the name or security identifier doesn't represent an actual user or group, an error is written and nothing is returned. .LINK Test-CIdentity .LINK Resolve-CIdentityName .LINK http://msdn.microsoft.com/en-us/library/system.security.principal.securityidentifier.aspx .LINK http://msdn.microsoft.com/en-us/library/windows/desktop/aa379601.aspx .LINK ConvertTo-CSecurityIdentifier .LINK Resolve-CIdentityName .LINK Test-CIdentity .OUTPUTS Carbon_Accounts_Identity. .EXAMPLE Resolve-CIdentity -Name 'Administrators' Returns an object representing the `Administrators` group. .EXAMPLE Resolve-CIdentity -SID 'S-1-5-21-2678556459-1010642102-471947008-1017' Demonstrates how to use a SID in SDDL form to convert a SID into an identity. .EXAMPLE Resolve-CIdentity -SID ([Security.Principal.SecurityIdentifier]::New()'S-1-5-21-2678556459-1010642102-471947008-1017') Demonstrates that you can pass a `SecurityIdentifier` object as the value of the SID parameter. .EXAMPLE Resolve-CIdentity -SID $sidBytes Demonstrates that you can use a byte array that represents a SID as the value of the `SID` parameter. #> [CmdletBinding()] param( # The name of the identity to return. [Parameter(Mandatory, ParameterSetName='ByName', Position=0)] [string] $Name, # The SID of the identity to return. Accepts a SID in SDDL form as a `string`, a # `System.Security.Principal.SecurityIdentifier` object, or a SID in binary form as an array of bytes. [Parameter(Mandatory , ParameterSetName='BySid')] [Object] $SID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'BySid') { $SID = ConvertTo-CSecurityIdentifier -SID $SID if (-not $SID) { return } $sidBytes = [byte[]]::New($SID.BinaryLength) $SID.GetBinaryForm($sidBytes, 0) $account = Invoke-AdvapiLookupAccountSid -Sid $sidBytes if (-not $account) { Write-Error -Message "SID ""${SID}"" not found." -ErrorAction $ErrorActionPreference return } return [Carbon_Accounts_Identity]::New($account.ReferencedDomainName, $account.Name, $SID, $account.Use) } if ($Name.StartsWith('.\')) { $username = $Name.Substring(2) $Name = "$([Environment]::MachineName)\${username}" $identity = Resolve-CIdentity -Name $Name if (-not $identity) { $Name = "BUILTIN\${username}" $identity = Resolve-CIdentity -Name $Name } return $identity } if ($Name.Equals("LocalSystem", [StringComparison]::InvariantCultureIgnoreCase)) { $Name = "NT AUTHORITY\SYSTEM" } $account = Invoke-AdvapiLookupAccountName -AccountName $Name if (-not $account) { Write-Error -Message "Identity ""${Name}"" not found." -ErrorAction $ErrorActionPreference return } $sid = [SecurityIdentifier]::New($account.Sid, 0) $ntAccount = $sid.Translate([NTAccount]) $domainName,$accountName = $ntAccount.Value.Split('\', 2) if (-not $accountName) { $accountName = $domainName $domainName = '' } return [Carbon_Accounts_Identity]::New($domainName, $accountName, $sid, $account.Use) } function Resolve-CIdentityName { <# .SYNOPSIS Determines the full, NT identity name for a user or group. .DESCRIPTION `Resolve-CIdentityName` resolves a user/group name into its full, canonical name, used by the operating system. For example, the local Administrators group is actually called BUILTIN\Administrators. With a canonical username, you can unambiguously compare identities on objects that contain user/group information. If unable to resolve a name into an identity, `Resolve-CIdentityName` returns nothing. If you want to get full identity information (domain, type, sid, etc.), use `Resolve-CIdentity`. You can also resolve a SID into its identity name. The `SID` parameter accepts a SID in SDDL form as a `[String]`, a `[System.Security.Principal.SecurityIdentifier]` object, or a SID in binary form as an array of bytes. If the SID no longer maps to an active account, you'll get the original SID in SDDL form (as a string) returned to you. .LINK ConvertTo-CSecurityIdentifier .LINK Resolve-CIdentity .LINK Test-CIdentity .LINK http://msdn.microsoft.com/en-us/library/system.security.principal.securityidentifier.aspx .LINK http://msdn.microsoft.com/en-us/library/windows/desktop/aa379601.aspx .OUTPUTS string .EXAMPLE Resolve-CIdentityName -Name 'Administrators' Returns `BUILTIN\Administrators`, the canonical name for the local Administrators group. #> [CmdletBinding(DefaultParameterSetName='ByName')] [OutputType([String])] param( # The name of the identity to return. [Parameter(Mandatory, ParameterSetName='ByName', Position=0)] [String] $Name, # Get an identity's name from its SID. Accepts a SID in SDDL form as a `string`, a # `System.Security.Principal.SecurityIdentifier` object, or a SID in binary form as an array of bytes. [Parameter(Mandatory, ParameterSetName='BySid')] [Object] $SID ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState if ($PSCmdlet.ParameterSetName -eq 'ByName') { return Resolve-CIdentity -Name $Name -ErrorAction Ignore | Select-Object -ExpandProperty 'FullName' } $id = Resolve-CIdentity -Sid $SID -ErrorAction Ignore if ($id) { return $id.FullName } return $SID.ToString() } function Test-CIdentity { <# .SYNOPSIS Tests that a name is a valid Windows local or domain user/group. .DESCRIPTION Uses the Windows `LookupAccountName` function to find an identity. If it can't be found, returns `$false`. Otherwise, it returns `$true`. Use the `PassThru` switch to return a `[Carbon_Accounts_Identity]` object (instead of `$true` if the identity exists). .LINK Resolve-CIdentity .LINK Resolve-CIdentityName .EXAMPLE Test-CIdentity -Name 'Administrators Tests that a user or group called `Administrators` exists on the local computer. .EXAMPLE Test-CIdentity -Name 'CARBON\Testers' Tests that a group called `Testers` exists in the `CARBON` domain. .EXAMPLE Test-CIdentity -Name 'Tester' -PassThru Tests that a user or group named `Tester` exists and returns a `[Carbon_Accounts_Identity]` object if it does. #> [CmdletBinding()] param( # The name of the identity to test. [Parameter(Mandatory)] [string] $Name, # Returns a `Carbon.Identity` object if the identity exists. [switch] $PassThru ) Set-StrictMode -Version 'Latest' Use-CallerPreference -Cmdlet $PSCmdlet -Session $ExecutionContext.SessionState $identity = Resolve-CIdentity -Name $Name -ErrorAction Ignore if (-not $identity) { return $false } if ($PassThru) { return $identity } return $true } 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) } } } |