PSNativeCmdDevKit.psm1
|
#Region './Public/Add-SudoPreferenceRule.ps1' -1 <# .SYNOPSIS Adds or changes a sudo preference rule. .DESCRIPTION Registers a command-specific sudo rule or enables or disables sudo for every native command. Parameter filters must be script blocks or the wildcard string '*'. Script blocks are retained without recompilation. .PARAMETER Executable Specifies the executable to which the rule applies. Use '*' with a wildcard or constant script-block filter to configure all commands. .PARAMETER ParameterFilterRule Specifies '*' to match every argument list or a script block that evaluates the command arguments through its automatic $args variable. .PARAMETER EnableSudoForAllCommands Enables sudo for every native command. .PARAMETER DisableSudoForAllCommands Disables the global sudo preference without deleting command rules. .PARAMETER SudoUser Specifies the user supplied to `sudo -u` when the matching rule applies. .EXAMPLE Add-SudoPreferenceRule -Executable 'dpkg' -ParameterFilterRule { $args -contains '--install' } Adds a rule that uses sudo for dpkg installation commands. .EXAMPLE Add-SudoPreferenceRule -EnableSudoForAllCommands -SudoUser 'root' Enables sudo for all native commands and selects the root user. #> function Add-SudoPreferenceRule { param ( [Parameter(ParameterSetName = 'Sudo', Mandatory = $true)] [Alias('Command')] # The binary or command the rule will affect. [string] $Executable, [Parameter(ParameterSetName = 'Sudo', Mandatory = $true)] # The Parameter filter to be evaluated for the command. # if you want to use sudo for an Executable, regardless of the parameters, use: # `-ParameterFilterRule *` or `-ParameterFilterRule {$true}` # Otherwise, you can evaluate the Parameters to be used, populated the $Args variable: # `-ParameterFilterRule {$args -contains '-i' -or $args -contains '--install'}` [object] $ParameterFilterRule, [Parameter(ParameterSetName = 'SudoAll', Mandatory = $true)] # This will Enable sudo for any command, but won't destroy your # registered settings. You can set a $SudoUser to be used along. [switch] $EnableSudoForAllCommands, [Parameter(ParameterSetName = 'NoSudoAll', Mandatory = $true)] # This will ensure sudo is not automatically added to each command, # instead it will use the Sudo Preference rules registered with `Add-SudoPreferenceRule`. [switch] $DisableSudoForAllCommands, [Parameter(ParameterSetName = 'Sudo')] [Parameter(ParameterSetName = 'SudoAll')] # The executable that is invoked with sudo should be run as this user. # the resulting command invoked will be `sudo <sudo user> <executable> <parameters>`. [string] $SudoUser ) if ($script:SudoPreferenceRules -isnot [System.Collections.ArrayList]) { # There is no default rules store, let's create an array list $script:SudoPreferenceRules = [System.Collections.ArrayList]::new() } if ($PSCmdlet.ParameterSetName -eq 'Sudo' -and $ParameterFilterRule -isnot [scriptblock] -and $ParameterFilterRule -ne '*') { throw [System.ArgumentException]::new( 'ParameterFilterRule must be a ScriptBlock or the wildcard string ''*''.' ) } if ($EnableSudoForAllCommands.IsPresent -or $DisableSudoForAllCommands.IsPresent) { $Script:SudoAll = switch ($PSCmdlet.ParameterSetName) { NoSudoAll { $false } SudoAll { $true } } # If sudoUser is specified, set to SudoAllAs. Clean up if disabling SudoAll $script:SudoAllAs = $SudoUser return } elseif ($Executable -eq '*') { $Script:SudoAll = switch ($ParameterFilterRule) { '*' { $true } default { [bool]$ParameterFilterRule.Invoke() } } $script:SudoAllAs = $SudoUser } $index = $null if (Get-SudoPreferenceRule -Executable $Executable -ParameterFilterRule $ParameterFilterRule) { Write-Verbose "Sudo Preference Rule found. Replacing" $index = [int](Remove-SudoPreferenceRule -Executable $Executable -ParameterFilterRule $ParameterFilterRule) } # copy hash with Executable, ParameterFilterRule, and SudoUser if present $newRule = @{ Executable = $Executable ParameterFilterRule = $ParameterFilterRule Sudo = $true SudoAs = $SudoUser } if ($null -ne $index) { Write-Debug "Replacing Sudo rule for '$Executable' with filter '$ParameterFilterRule' at index $index" $null = $script:SudoPreferenceRules.Insert($index, $newRule) } else { Write-Debug "Adding Sudo rule for '$Executable' with filter '$ParameterFilterRule'" $null = $script:SudoPreferenceRules.Add($newRule) } } #EndRegion './Public/Add-SudoPreferenceRule.ps1' 145 #Region './Public/Get-PropertyHashFromListOutput.ps1' -1 <# .SYNOPSIS Converts line-oriented native output into a property hashtable. .DESCRIPTION Parses lines containing named property and value groups, normalizes property names, appends continuation lines, and routes redirected standard-error records to a caller-provided handler. .PARAMETER Output Specifies one or more native-command output lines or error records. .PARAMETER Regex Specifies the regular expression used to capture named `property` and `val` groups. .PARAMETER AllowedPropertyName Specifies property names to retain at the top level. The default '*' accepts every parsed property. .PARAMETER DiscardExtraProperties Discards parsed properties that are not listed in AllowedPropertyName. .PARAMETER AddExtraPropertiesAsKey Specifies the nested hashtable key used for properties that are not in AllowedPropertyName. .PARAMETER ErrorHandling Specifies the script block that receives redirected standard-error records. .EXAMPLE 'Name: PowerShell', 'Version: 7.6' | Get-PropertyHashFromListOutput Returns a hashtable containing Name and Version. #> function Get-PropertyHashFromListOutput { [CmdletBinding(DefaultParameterSetName = 'AddExtraPropertiesUnderKey')] [OutputType([hashtable])] param ( [Parameter(ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Mandatory = $true)] [Object] # Output from a command, typically the result of Invoke-LinuxCommand. # Error records will be handled by the scriptblock in -ErrorHandling parameter. # The latter defaults to send the error record to Write-Error. $Output, [Parameter()] # Regex with 'property' & 'val' Named groups # of a string to extract an hashtable key/value pair from a string. [regex] $Regex = '^\s*(?<property>[\w-\s]*):\s*(?<val>.*)', [Parameter()] # List of property names allowed to be parsed. # Default to '*' for all properties, otherwise the parsed properties # not listed here will either be discarded if -DiscardExtraProperties is set # or will be added to a hashtable under the key named $AddExtraPropertiesAsKey. [string[]] $AllowedPropertyName = '*', [Parameter(ValueFromPipelineByPropertyName = $true, ParameterSetName = 'DiscardExtraProperties')] # When only a limited number of Property named is allowed using -AllowedPropertyName # parameter, the extra properties will be discarded. [switch] $DiscardExtraProperties, [Parameter(ParameterSetName = 'AddExtraPropertiesUnderKey')] # When only a limited number of Property named is allowed using -AllowedPropertyName # parameter, the extra properties will be added under the `$property[$AddExtraPropertiesAsKey]` # hash. For instance, `$property['ExtraProperties']['NotAllowedPropertyName'] = $ParsedValue` [string] $AddExtraPropertiesAsKey = 'ExtraProperties', [Parameter()] # When the output of a native command has had its `STDERR` redirected # using `2>&1`, we'll send the ErrorRecords (output from STDERR) to # this scriptblock. By default: `$errorRecord | &{ Write-Error $_}`. [scriptblock] $ErrorHandling = { Write-Error $_ } ) begin { $properties = @{} if (-not $DiscardExtraProperties.isPresent) { $properties[$AddExtraPropertiesAsKey] = @{} } } process { foreach ($line in $Output) { Write-Debug "Output Line: $line" if ($line -is [System.Management.Automation.ErrorRecord]) { $line | &$ErrorHandling } elseif ($line -match $Regex) { $propertyName = $Matches.property.replace('-','').replace(' ','') if ($AllowedPropertyName -contains '*' -or $AllowedPropertyName -contains $propertyName) { $properties.Add($propertyName, $Matches.val) } else { if (-not $DiscardExtraProperties.isPresent) { Write-Debug " Adding Property '$propertyName' to $AddExtraPropertiesAsKey" $properties[$AddExtraPropertiesAsKey].Add($propertyName, $Matches.val) } } $lastProperty = $propertyName } else { if (-not $lastProperty) { Write-Verbose $line } elseif ($AllowedPropertyName -contains '*' -or $AllowedPropertyName -contains $lastProperty) { Write-Debug " Adding second line to property $lastProperty" $properties[$lastProperty] += "`n" + $line.TrimEnd() } elseif (-not $DiscardExtraProperties.IsPresent) { $properties[$AddExtraPropertiesAsKey][$lastProperty] += $line.Trim() } } } } end { if ($properties[$AddExtraPropertiesAsKey].Count -eq 0) { Write-Debug "No Extra properties where found, removing unnecessary key '$AddExtraPropertiesAsKey'" $properties.Remove($AddExtraPropertiesAsKey) } $properties } } #EndRegion './Public/Get-PropertyHashFromListOutput.ps1' 153 #Region './Public/Get-SudoPreference.ps1' -1 <# .SYNOPSIS Resolves the sudo preference for a native command. .DESCRIPTION Returns the first global or command-specific sudo preference whose executable and argument filter match the supplied invocation. .PARAMETER Executable Specifies the executable for which to resolve a sudo preference. .PARAMETER Parameters Specifies the native argument values evaluated by script-block filters. .EXAMPLE Get-SudoPreference -Executable 'dpkg' -Parameters '--install', 'package.deb' Returns the matching sudo preference, when one is registered. #> function Get-SudoPreference { [CmdletBinding()] [OutputType([hashtable])] param ( [Parameter(Mandatory = $true)] [Alias('Command')] # The binary or command to be executed. [string] $Executable, [Parameter()] # List of parameters to pass to the invocation that will be # evaluated against the registered Sudo Preference Rules. [String[]] $Parameters ) if ($script:SudoAll) { @{ Sudo = $true SudoAs = $script:SudoAllAs } } elseif ($script:SudoPreferenceRules) { $RuleMatchFound = $script:SudoPreferenceRules | Where-Object -FilterScript { $Executable -eq $_.Executable -and ($_.ParameterFilterRule -eq '*' -or $_.ParameterFilterRule.Invoke($Parameters)) } | Select-Object -First 1 if ($RuleMatchFound) { return [hashtable]$RuleMatchFound } else { Write-Debug "No matching rules for '$Executable' with params '$Parameters'" } } } #EndRegion './Public/Get-SudoPreference.ps1' 64 #Region './Public/Get-SudoPreferenceRule.ps1' -1 <# .SYNOPSIS Gets registered sudo preference rules. .DESCRIPTION Returns all registered rules or filters rules by executable and, optionally, by the exact parameter-filter object. .PARAMETER Executable Specifies the executable whose registered rules should be returned. .PARAMETER ParameterFilterRule Specifies the exact script block or wildcard filter to match. .PARAMETER All Returns every registered sudo preference rule. .EXAMPLE Get-SudoPreferenceRule -Executable 'dpkg' Returns all rules registered for dpkg. #> function Get-SudoPreferenceRule { [CmdletBinding(DefaultParameterSetName = 'all')] [OutputType([System.Object[]])] param ( [Parameter(ParameterSetName = 'byCommand', Mandatory = $true)] [Alias('Command')] # The binary or command to be executed. [string] $Executable, [Parameter(ParameterSetName = 'byCommand')] [object] $ParameterFilterRule, [Parameter(ParameterSetName = 'all')] [switch] $All ) if ($script:SudoPreferenceRules -isnot [System.Collections.ArrayList]) { # There is no default rules store, let's create an array list and return it $script:SudoPreferenceRules = [System.Collections.ArrayList]::new() } if ($PSCmdlet.ParameterSetName -eq 'All') { $script:SudoPreferenceRules } else { $script:SudoPreferenceRules.Where{ $_.Executable -eq $Executable -and $( if ($ParameterFilterRule -and $ParameterFilterRule -ne '*') { $_.ParameterFilterRule -eq $ParameterFilterRule } else { $true } ) } } } #EndRegion './Public/Get-SudoPreferenceRule.ps1' 74 #Region './Public/Invoke-NativeCommand.ps1' -1 <# .SYNOPSIS Invokes a native command with an argument array. .DESCRIPTION Invokes an executable without constructing PowerShell source code, optionally applies sudo preferences on Linux or macOS, and redirects standard error into the success stream for downstream parsing. .PARAMETER Executable Specifies the native executable or command to invoke. .PARAMETER Sudo Invokes the command through sudo on Linux or macOS. .PARAMETER SudoAs Invokes the command through `sudo -u` for the specified user on Linux or macOS. .PARAMETER Parameters Specifies native argument values in their required order. .EXAMPLE Invoke-NativeCommand -Executable 'git' -Parameters '--version' Invokes git and returns its combined standard output and standard error. #> function Invoke-NativeCommand { [cmdletBinding()] param ( [Parameter(Mandatory = $true)] [Alias('Command')] # The binary or command you would like to execute. [string] $Executable, [Parameter()] # Whether you want to sudo the command invocation, on non-windows OSes. # If you want to sudo as a different user, use the parameter `-SudoAs`. [switch] $Sudo, [Parameter()] # Specify a user to sudo he command as. i.e.: `sudo otheruser ls -alh` [String] $SudoAs, [Parameter()] # list of Parameters to pass to the invocation. # For binaries and commands requiring a specific order # make sure it is respected as no further check is done. [String[]] $Parameters ) # If Sudo or SudoAs is not specified, lookup in the Module variable DefaultCommandToSudo if ( -not ($PSBoundParameters.ContainsKey('Sudo') -or $PSBoundParameters.ContainsKey('SudoAs')) ) { if ($DefaultSudo = Get-SudoPreference @PSBoundParameters) { $Sudo = $DefaultSudo.Sudo $SudoAs = $DefaultSudo.SudoAs } } [string] $Command = $Executable [string[]] $CommandParameters = @() if ($SudoAs -and ($IsLinux -or $IsMacOS)) { $Command = 'sudo' $CommandParameters += '-u' $CommandParameters += $SudoAs $CommandParameters += $Executable } elseif ($Sudo -and ($IsLinux -or $IsMacOS)) { $Command = 'sudo' $CommandParameters += $Executable } $CommandParameters += $Parameters Write-Verbose -Message "Running #> $Command $CommandParameters" # Stream output through the pipeline and mix STDERR with STDOUT. & $Command @CommandParameters 2>&1 } #EndRegion './Public/Invoke-NativeCommand.ps1' 92 #Region './Public/Remove-SudoPreferenceRule.ps1' -1 <# .SYNOPSIS Removes registered sudo preference rules. .DESCRIPTION Removes rules by executable and filter, by internal index, or clears every registered rule. .PARAMETER Executable Specifies the executable whose matching rules should be removed. .PARAMETER ParameterFilterRule Specifies the exact script block or wildcard filter to remove. .PARAMETER Index Specifies the internal zero-based rule index to remove. .PARAMETER All Removes every registered sudo preference rule. .EXAMPLE Remove-SudoPreferenceRule -Executable 'dpkg' -ParameterFilterRule '*' Removes wildcard sudo rules registered for dpkg. #> function Remove-SudoPreferenceRule { [cmdletBinding()] param ( [Parameter(ParameterSetName = 'ByValue', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [Alias('Command')] # The executable that has the rule applied to. [string] $Executable, [Parameter(ParameterSetName = 'ByValue', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] # The parameter filter rule to match with the executable to remve. [object] $ParameterFilterRule, [Parameter(Dontshow = $true, ParameterSetName = 'ByIndex', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] # Remove the Rule stored in the module's $script:SudoPreferenceRules by its index (advanced user only) [int] $index, [Parameter(ParameterSetName = 'All', Mandatory = $true)] # Remove all previously registered rules. [switch] $All ) begin { if ($script:SudoPreferenceRules -isnot [System.Collections.ArrayList]) { # There is no default rules store return } } process { if ($PSCmdlet.ParameterSetName -eq 'ByIndex') { $script:SudoPreferenceRules.RemoveAt($Index) return } elseif ($PSCmdlet.ParameterSetName -eq 'All') { $script:SudoPreferenceRules.Clear() return } $CurrentIndex = 0 $indexesToRemove = $script:SudoPreferenceRules.Foreach{ if ($_.Executable -eq $Executable -and ($_.ParameterFilterRule -eq '*' -or $_.ParameterFilterRule -eq $ParameterFilterRule) ) { $CurrentIndex } $CurrentIndex++ } $indexesToRemove | Sort-Object -Descending | ForEach-Object { $script:SudoPreferenceRules.RemoveAt($_) # return the Indexes where the rule has been removed $_ } } } #EndRegion './Public/Remove-SudoPreferenceRule.ps1' 97 |