core/modules/monkeyruleset/private/Format-DataFromExpression.ps1
# Monkey365 - the PowerShell Cloud Security Tool for Azure and Microsoft 365 (copyright 2022) by Juan Garrido # # 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 Format-DataFromExpression { <# .SYNOPSIS Construct a PowerShell expression from a psObject object and format data .DESCRIPTION Construct a PowerShell expression from a psObject object and format data .INPUTS .OUTPUTS .EXAMPLE .NOTES Author : Juan Garrido Twitter : @tr1ana File Name : Format-DataFromExpression Version : 1.0 .LINK https://github.com/silverhack/monkey365 #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "", Scope="Function")] [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipeline = $True, HelpMessage="data object")] [Object]$InputObject, [Parameter(Mandatory=$true, HelpMessage="Expressions object")] [AllowNull()] [Object]$Expressions, [Parameter(Mandatory=$false, HelpMessage="Expand object")] [AllowNull()] [AllowEmptyString()] [String]$ExpandObject, [Parameter(Mandatory=$false, HelpMessage="Excluded properties")] [System.Array]$ExcludedProperties = @("Raw","RawData","raw_data","rawPolicy","rawObject"), [Parameter(Mandatory=$false, HelpMessage="Specifies the number of objects to select from the beginning of an array of input objects")] [AllowNull()] [System.Int32]$First, [Parameter(Mandatory=$false, HelpMessage="Specifies the number of objects to select from the end of an array of input objects.")] [AllowNull()] [System.Int32]$Last, [Parameter(Mandatory=$false, HelpMessage="Skips (doesn't select) the specified number of items.")] [AllowNull()] [System.Int32]$Skip, [Parameter(Mandatory=$false, HelpMessage="Selects objects from an array based on their index values.")] [AllowNull()] [System.Int32]$Index, [Parameter(Mandatory=$false, HelpMessage="Rule Name. Used for debug purposes")] [AllowNull()] [AllowEmptyString()] [String]$RuleName ) Begin{ Set-StrictMode -Off $allItems = [System.Collections.Generic.List[System.Object]]::new() #Create Select-Object new param $MetaData = New-Object -TypeName "System.Management.Automation.CommandMetaData" (Get-Command -Name "Select-Object") $selectObjectParam = [ordered]@{} If($null -ne $MetaData){ $param = $MetaData.Parameters.Keys.Where({$_.ToLower() -ne "inputobject"}) ForEach($p in $param.GetEnumerator()){ If($PSBoundParameters.ContainsKey($p)){ $selectObjectParam.Add($p,$PSBoundParameters[$p]) } } } #Internal Function Function Get-PsExpression { <# .SYNOPSIS Construct a PowerShell expression from a psObject object and format data .DESCRIPTION Construct a PowerShell expression from a psObject object and format data .INPUTS .OUTPUTS .EXAMPLE .NOTES Author : Juan Garrido Twitter : @tr1ana File Name : Format-DataFromExpression Version : 1.0 .LINK https://github.com/silverhack/monkey365 #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true, ValueFromPipeline = $True, HelpMessage="data object")] [Object]$InputObject, [Parameter(Mandatory=$true, HelpMessage="Expressions object")] [AllowNull()] [Object]$Expressions, [Parameter(Mandatory=$false, HelpMessage="Expand object")] [AllowNull()] [AllowEmptyString()] [String]$ExpandObject ) Process{ Try{ $allExpressions = [System.Collections.Generic.List[System.Object]]::new() #Set null $_expressions = $null; #check if PsObject $isPsCustomObject = ([System.Management.Automation.PSCustomObject]).IsAssignableFrom($PSBoundParameters['Expressions'].GetType()) #check if PsObject $isPsObject = ([System.Management.Automation.PSObject]).IsAssignableFrom($PSBoundParameters['Expressions'].GetType()) If(-NOT $isPsCustomObject -and -NOT $isPsObject){ #Check if dictionary If(([System.Collections.IDictionary]).IsAssignableFrom($Expressions.GetType())){ #Check if * in properties If($PSBoundParameters['Expressions'].Values.Where({$_ -eq "*"}) -or $PSBoundParameters['Expressions'].keys.Where({$_ -eq "*"})){ $_expressions = "*" } Else{ #Set new psObject $_expressions = New-Object -TypeName PSCustomObject -Property $PSBoundParameters['Expressions'] #Set pscustomobject $isPsCustomObject = $true } } ## Iterate over all child objects in cases in which InputObject is an array ElseIf ($PSBoundParameters['Expressions'] -is [System.Collections.IEnumerable] -and $PSBoundParameters['Expressions'] -isnot [string]){ If($PSBoundParameters['Expressions'].Contains('*')){ $_expressions = "*" } ElseIf($PSBoundParameters['Expressions'].Count -eq 0){ $_expressions = "*" } Else{ Try{ $props = [ordered]@{} Foreach($expr in $PSBoundParameters['Expressions']){ If(![System.String]::IsNullOrEmpty($expr)){ If($expr.Trim().ToString().Contains('.')){ [void]$props.Add($expr,$expr.Split('.')[-1].Trim().ToString()); } Else{ [void]$props.Add($expr,$expr); } } } #Set new psObject $_expressions = New-Object -TypeName PSCustomObject -Property $props #Set pscustomobject $isPsCustomObject = $true } Catch{ Write-Error "Unable to format expression from array" } } } ElseIf($PSBoundParameters['Expressions'] -is [System.String]){ If($PSBoundParameters['Expressions'] -eq '*'){ $_expressions = "*" } ElseIf([System.String]::IsNullOrEmpty($PSBoundParameters['Expressions'])){ $_expressions = "*" } ElseIf([System.String]::IsNullOrWhiteSpace($PSBoundParameters['Expressions'])){ $_expressions = "*" } Else{ Try{ $props = [ordered]@{} If($PSBoundParameters['Expressions'].Trim().ToString().Contains('.')){ [void]$props.Add($PSBoundParameters['Expressions'],$PSBoundParameters['Expressions'].Split('.')[-1].Trim().ToString()); } Else{ [void]$props.Add($PSBoundParameters['Expressions'],$PSBoundParameters['Expressions']); } #Set new psObject $_expressions = New-Object -TypeName PSCustomObject -Property $props #Set pscustomobject $isPsCustomObject = $true } Catch{ Write-Error "Unable to format expression from string" } } } Else{ Write-Warning "Unable to evaluate expressions" } } Else{ If($PSBoundParameters['Expressions'].PsObject.Properties.Name -match "\*"){ If($PSBoundParameters.ContainsKey('ExpandObject') -and $PSBoundParameters['ExpandObject']){ $newHashTable = [ordered]@{} $newProps = $InputObject | Select-Object -ExpandProperty $PSBoundParameters['ExpandObject'] | Select-Object -First 1 | Select-Object -Property {$_.Psobject.Properties.Name} | Select-Object -ExpandProperty '$_.Psobject.Properties.Name' If($null -ne $newProps){ #Create new object Foreach($elem in $PSBoundParameters['Expressions'].PsObject.Properties){ If($elem.Name -notmatch $PSBoundParameters['ExpandObject']){ [void]$newHashTable.Add($elem.Name,$elem.Value) } } Foreach($prop in $newProps){ [void]$newHashTable.Add(("{0}.{1}" -f $PSBoundParameters['ExpandObject'],$prop),$prop) } #Create new object $_expressions = New-Object -TypeName PSCustomObject -Property $newHashTable } Else{ Write-Verbose ("Unable to get PsObject properties from {0}. Empty object returned" -f $PSBoundParameters['ExpandObject']) } } Else{ $_expressions = "*" } } ElseIf(-not $PSBoundParameters['Expressions'].PsObject.Properties.GetEnumerator().MoveNext()){ #Empty psObject $_expressions = "*" } Else{ $_expressions = $PSBoundParameters['Expressions']; } } If($null -ne $_expressions){ #String expressions won't be evaluated If($_expressions -isnot [System.String]){ If($PSBoundParameters.ContainsKey('ExpandObject') -and $PSBoundParameters['ExpandObject'] -and ($isPsCustomObject -or $isPsObject)){ ForEach($element in $_expressions.PsObject.Properties){ $property = [System.Text.StringBuilder]::new() If($element.Name.StartsWith($PSBoundParameters['ExpandObject'])){ $propName = $element.Name.Replace(("{0}." -f $PSBoundParameters['ExpandObject']),'') If($propName.Trim().ToString().Contains('@')){ [void]$property.Append(('"{0}".' -f $propName.Trim().ToString())); } Else{ ForEach($str in $propName.Split('.')){ If($str.Trim().ToString().Contains(' ')){ [void]$property.Append(('"{0}".' -f $str.Trim().ToString())); } Else{ [void]$property.Append(("{0}." -f $str.Trim().ToString())); } } } #Remove last character $property.Length--; #Check for null If([System.String]::IsNullOrEmpty($element.value) -or [System.String]::IsNullOrWhiteSpace($element.Value)){ $element.Value = $property.ToString().Split('.')[-1].Replace(' ','').Replace('"',"") } #Create expression Try{ $newExpression = [ordered]@{ Name = $element.value; Expression = [scriptblock]::Create(('$_.{0}' -f $property.ToString())) } [void]$allExpressions.Add($newExpression); } Catch{ Write-Warning "Unable to create expression" Write-Warning $_.Exception.Message } } Else{ If($element.Name.Trim().ToString().Contains('@')){ [void]$property.Append(('"{0}".' -f $element.Name.Trim().ToString())); } Else{ ForEach($str in $element.Name.Split('.')){ If($str.Trim().ToString().Contains('@') -or $str.Trim().ToString().Contains(' ')){ [void]$property.Append(('"{0}".' -f $str.Trim().ToString())); } Else{ [void]$property.Append(("{0}." -f $str.Trim().ToString())); } } } #Remove last character $property.Length--; #Check for null If([System.String]::IsNullOrEmpty($element.value) -or [System.String]::IsNullOrWhiteSpace($element.Value)){ $element.Value = $property.ToString().Split('.')[-1].Replace(' ','').Replace('"',"") } #Create new expression Try{ $newExpression = [ordered]@{ Name = $element.value; Expression = [scriptblock]::Create(('$elem.{0}' -f $property.ToString())) } } Catch{ Write-Warning "Unable to create expression" Write-Warning $_.Exception.Message } [void]$allExpressions.Add($newExpression); } } } Else{ ForEach($element in $_expressions.PsObject.Properties){ #Set stringbuilder $property = [System.Text.StringBuilder]::new() If($element.Name.Trim().ToString().Contains('@')){ [void]$property.Append(('"{0}".' -f $element.Name.Trim().ToString())); } Else{ ForEach($str in $element.Name.Split('.')){ If($str.Trim().ToString().Contains(' ')){ [void]$property.Append(('"{0}".' -f $str.Trim().ToString())); } Else{ [void]$property.Append(("{0}." -f $str.Trim().ToString())); } } } #Remove last character $property.Length--; If([System.String]::IsNullOrEmpty($element.value) -or [System.String]::IsNullOrWhiteSpace($element.Value)){ $element.Value = $property.ToString().Split('.')[-1].Replace(' ','').Replace('"',"") } #Set new expression Try{ $newExpression = [ordered]@{ Name = $element.value; Expression = [scriptblock]::Create(('$_.{0}' -f $property.ToString())) } [void]$allExpressions.Add($newExpression); } Catch{ Write-Warning "Unable to create expression" Write-Warning $_.Exception.Message } } } } Else{ #Add expression [void]$allExpressions.Add($_expressions); } } Else{ Write-Verbose "Unable to evaluate expression. An empty property or object was found" } #return Object $allExpressions } Catch{ Write-Error $_ Write-Warning "Unable to get expressions from object" } } } } Process{ #Create Get-PsExpression param $MetaData = New-Object -TypeName "System.Management.Automation.CommandMetaData" (Get-Command -Name "Get-PsExpression") $psExpressionParam = [ordered]@{} If($null -ne $MetaData){ $param = $MetaData.Parameters.Keys.Where({$_.ToLower() -ne "inputobject"}) ForEach($p in $param.GetEnumerator()){ If($PSBoundParameters.ContainsKey($p)){ $psExpressionParam.Add($p,$PSBoundParameters[$p]) } } } Try{ If($PSBoundParameters.ContainsKey('ExpandObject') -and $PSBoundParameters['ExpandObject']){ ForEach($elem in @($InputObject)){ #Get Expressions $_expressions = $elem | Get-PsExpression @psExpressionParam If($PSBoundParameters['ExpandObject'].Trim().ToString().Contains('.')){ If(($elem.psobject.Methods.Where({$_.MemberType -eq 'ScriptMethod' -and $_.Name -eq 'GetPropertyByPath'})).Count -gt 0){ $subelements = $elem.GetPropertyByPath($PSBoundParameters['ExpandObject'].Trim()) If($null -ne $subelements){ $_allItems = $subelements | Select-Object $_expressions -ErrorAction SilentlyContinue Foreach($item in @($_allItems)){ [void]$allItems.Add($item); } } } Else{ Write-Warning "GetPropertyByPath method was not loaded" } } Else{ #Get element $subelements = $elem | Select-Object -ExpandProperty $PSBoundParameters['ExpandObject'] -ErrorAction Ignore If($null -ne $subelements){ $_allItems = $subelements | Select-Object $_expressions -ErrorAction Ignore If($null -ne $_allItems){ Foreach($item in @($_allItems)){ [void]$allItems.Add($item); } } } } } } Else{ ForEach($newItem in @($InputObject)){ #Get Expressions $_expressions = $newItem | Get-PsExpression @psExpressionParam try{ $_allItems = $newItem | Select-Object $_expressions -ErrorAction SilentlyContinue Foreach($item in @($_allItems)){ [void]$allItems.Add($item); } } Catch{ Write-Error $_ Write-Warning "Unable to get expressions from object" } } } } Catch{ Write-Error $_ Write-Warning "Unable to get expressions from object" } } End{ #Excluded properties If($allItems.Count -gt 0){ $allItems | Select-Object * -ExcludeProperty $ExcludedProperties @selectObjectParam -ErrorAction Ignore } } } |