Create the "skeleton" of a custom PSObject from scratch - powershell

I'm trying to create a PSCustomObject that will later on be converted to JSON as the body of an API request.
This body basically has a structure to add / remove different information to a security management tool policy (filehash / filenames / certificates / IPs / file extensions / folders exceptions, on which technology etc.)
Here is an example of a final JSON with all the possibilities.
As a proof of concept, I'm focusing on the following part which is adding a filename & a filehash to the exception policy
{
"add": {
"applications": [
{
"processfile": {
"sha2": "6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea",
"name": "myfilename"
}
}
]
...
My goal is not to import this specific JSON as a custom Object, (I know this can be done through convertfrom-Json). It's to create some empty object, but with the correct structure matching the JSON format. This way, I would just have to populate and access information in my object like this :
PS C:\PSSymantecCloud> $obj.add.applications
processfile
-----------
#{sha2=6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea; name=myfilename}
PS C:\PSSymantecCloud> $obj.add.applications.processfile
sha2 name
---- ----
6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea myfilename
I've been trying to play around with Format-Custom CmdLet but without success.
Any suggestions on how to create such a custom PSObject ?

I'm fond of PowerShell classes so here is one way to do it with them. You can add new processfile with the AddProcessFile method that takes 2 arguments as in your Json:
class addjson {
[object] $add
addjson() {
$this.add = [pscustomobject]#{
applications = [System.Collections.Generic.List[object]]::new()
}
}
[void] AddProcessFile([string] $sha2, [string] $name) {
$this.add.applications.Add([pscustomobject]#{
processfile = [pscustomobject]#{
sha2 = $sha2
name = $name
}
})
}
}
$add = [addjson]::new()
$add.AddProcessFile('6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea', 'myFile')
$add.AddProcessFile('6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea', 'myFile2')
$add | ConvertTo-Json -Depth 4
Resulting Json would be:
{
"add": {
"applications": [
{
"processfile": {
"sha2": "6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea",
"name": "myFile"
}
},
{
"processfile": {
"sha2": "6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea",
"name": "myFile2"
}
}
]
}
}

Create an psObject shell, assign empty strings ““, or $false to the properties
$Object = New-Object PSObject -Property #{
Name = ““
SecondProperty= ““
yetAnotherProoerty= ““
}
Later u can assign your results like this:
$Object.name= $myResult
Or even add new member aka properties to the object like this:
Object | Add-Member -NotePropertyName Status -NotePropertyValue Done

You can also just take your sample text and convert it with convertto-json. e.g.
$HereString = #"
{
"add": {
"applications": [
{
"processfile": {
"sha2": "6ddc5c11925ab348eb0d390ec5179c1d655eb4bf70779f7a4e28b7db485d20ea",
"name": "myfilename"
}
}
]
}
}
"#
$PSObject = $HereString | ConvertFrom-Json
$PSObject | get-member

Related

How to Extract the variable values from terraform variables.tf file using PowerShell commands

I have a variables.tf file which contains all the project variables and im trying to fetch a variable values using PowerShell.
variables.tf
variable "products" {
default = [
"Product-1",
"Product-2",
"Product-3",
"Product-4"
]
}
variable "product_unified_container" {
default = [
"cont-data",
"cont-data-2"
]
}
variable "location" {
default = "westeurope"
}
Using PowerShell i need to be able to fetch the variable values for any variable I want.
Example : the command should give me a array of all the products variables in variables.tf if it has multiple values.
write-host $product_list
Product-1
Product-2
Product-3
Product-4
if the variable has one value then it should give me that value like "location" variable.
write-host $deployed_location
westeurope
I was going through a similar problem so, I can share a way using which you can extract the values.
The problem is it is easy to extract and manipulate values in a json or other format but in tf files it is not the same. So, I have basically used a workaround where I have to set the given file in a structure that the values are filled in one single line
So, variables.tf will look
variable "products" {
default = ["Product-1", "Product-2", "Product-3", "Product-4"]
}
variable "product_unified_container" {
default = ["cont-data","cont-data-2"]
}
variable "location" {
default = "westeurope"
}
Next comes the PS code to extract the values of variables-
$paramsArray = #()
[system.array]$params = Select-String -Path "variables.tf" -Pattern "default =" -SimpleMatch
if (!([string]::IsNullOrEmpty($Params)))
{
[system.array]$paramsStrings = $params -split {$_ -eq "="}
foreach ($paramString in $paramsStrings)
{
if (($paramString -match "TF-Template") -or ($paramString -match "tf:"))
{
#Write-Output $paramString
}
else
{
If ($paramsArray -notcontains $paramString)
{
$paramsArray+=$paramString
}
}
}
}
write-host $paramsArray
The output generated is-
Since this is an array you can iterate and use it later in the script.
I was able to solve this by below approach hope this will help someone with similar requirement.
Following Command will fetch any variable values present in variables.tf file, in this case variable "products" and assign it to another array variable.
$product_list = (Get-Content "variables.tf" -Raw) | Foreach-Object {
$_ -replace '(?s)^.*products', '' `
-replace '(?s)variable.+' `
-replace '[\[\],"{}]+' `
-replace ("default =","") | Where { $_ -ne "" } | ForEach { $_.Replace(" ","") } | Out-String | ForEach-Object { $_.Trim() }
}
foreach ( $Each_Product in $product_list.Split("`n")) {
write-host "Each Product Name : "$Each_Product
}
Output :
Each Product Name : Product-1
Each Product Name : Product-2
Each Product Name : Product-3
Each Product Name : Product-4

Set method as property of object in Powershell

I have an array of objects like this
$all = #(
#{ Name = 'First'; Method = { FirstMethod 1 }; Description = "First Description"; }
#{ Name = 'Second'; Description = "Second Description" }
#{ Name = 'Third'; Method = { ThirdMethod }; Description = "Third Description" }
)
Which every obeject has a Name (string), a Description (string), and a Method (which contains a function and its optional)
While the FirstMethod and SecondMethod looks like this:
Function FirstMethod
{
param($number)
Write-Host "$number - some other things"
return $number
}
Function ThirdMethod
{
Write-Host "Second called"
return 'test'
}
And I am iterating through all the items in $all and trying to call Method parameter if it exists:
Function RunAll
{
foreach($item in $all)
{
If($item.Method)
{
Write-Host "It has method and its running it"
$returned_from_method = $item.Method
Write-Host "Value returned from method: $returned_from_method"
}
Else
{
Write-Host "Does not have a method!"
}
}
}
So basically what I need here is that: when the loop is in the First item in array $returned_from_method = $item.Method it should return 1 (because it calls FirstMethod and passes 1. And when the loop is in the Third item in array it should return test (because it calls ThirdMethod).
Is there anyway I can achieve this?
The code you posted does neither define (custom) objects nor methods. It defines a list of hashtables where one key has a scriptblock value. Using dot-access on that key will just return the definition of the scriptblock, not invoke it.
Demonstration:
PS C:\> $ht = #{Name='foo'; Method={FirstMethod 1}; Description='bar'}
PS C:\> $ht.Method
FirstMethod 1
Even if you convert the hashtable to an object, that behavior does not change:
PS C:\> $obj = [PSCustomObject]$ht
PS C:\> $obj.Method
FirstMethod 1
To actually invoke the scriptblock you need to call the scriptblock's Invoke() method:
PS C:\> $ht.Method.Invoke()
1 - some other things
1
PS C:\> $obj.Method.Invoke()
1 - some other things
1
Whether the function called in the scriptblock is defined before or after creation of the hashtable or object doesn't matter, as long as it is defined before the scriptblock is invoked. The code in your own answer seems to work only because you replaced the scriptblock (curly brackets) with a grouping expression (parentheses). That means, however, that the "method" is evaluated upon definition of the hashtable and only the return value of the function is stored with the key. The Write-Host output is written to the console immediately and not stored with the key.
PS C:\> $ht = #{Name='foo'; Method=(FirstMethod 1); Description='bar'}
1 - some other things
PS C:\> $ht.Method
1
For creating an object with an actual (script)method you need to add a property with the correct type:
PS C:\> $obj | Add-Member -Name 'Method2' -Type ScriptMethod -Value {FirstMethod 2}
PS C:\> $obj.Method2()
2 - some other things
2
The code for creating your objects should thus look somewhat like this:
$obj1 = [PSCustomObject]#{
Name = 'First'
Description = 'First Description'
}
$obj1 | Add-Member -Name 'Method' -Type ScriptMethod -Value {FirstMethod 1}
$obj2 = [PSCustomObject]#{
Name = 'Second'
Description = 'Second Description'
}
$obj3 = [PSCustomObject]#{
Name = 'Third'
Description = 'Third Description'
}
$obj3 | Add-Member -Name 'Method' -Type ScriptMethod -Value {ThirdMethod}
$all = $obj1, $obj2, $obj3
If anyone is struggling with this one here's the answer:
The methods FirstMethod and ThirdMethod should be declared before the array
and then i needed to add the function inside (), and not {}.
Function FirstMethod
{
param($number)
Write-Host "$number - some other things"
return $number
}
Function ThirdMethod
{
Write-Host "Second called"
return 'test'
}
$all = #(
#{ Name = 'First'; Method = ( FirstMethod 1 ); Description = "First Description"; }
#{ Name = 'Second'; Description = "Second Description" }
#{ Name = 'Third'; Method = ( ThirdMethod ); Description = "Third Description" }
)
And it works fine now!

Remove PowerShell object properties that match pattern

Given an example object (coverted from JSON):
{
"Id": 1,
"Name": "Pablo",
"UnwantedProperty1XOXO": true,
"UnwantedProperty2XOXO": false,
"Things": [
{
"Name": "Something",
"UnwantedProperty3XOXO": true
}
]
...
}
How can I remove all properties that match a pattern? In the example I want to remove the three properties that end in XOXO.
My current approach is to use -ExcludeProperty like this:
$myObject | Select-Object -Property * -ExcludeProperty *XOXO
That only removes the first two properties. I need to reach deeper into the collection of Things as well. The object will change as well so I can't hardcode a check for Things and there could be many collections.
Indeed, Select-Object -ExcludeProperty does not act recursively - it only acts on the immediate properties - so a custom solution is needed.
Defining function Remove-Property, printed below, should provide the desired recursive logic:
$sampleJson = #'
{
"Id": 1,
"Name": "Pablo",
"UnwantedProperty1XOXO": true,
"UnwantedProperty2XOXO": false,
"Things": [
{
"Name": "Something",
"UnwantedProperty3XOXO": true
}
]
}
'#
$sampleJson | ConvertFrom-Json |
Remove-Property -NamePattern *XOXO |
ConvertTo-Json
An important aside: ConvertFrom-Json limits parsing to depth of just 2 levels by default, so you may have to specify a greater depth with -Depth <n>.
This problematic default behavior is discussed in GitHub issue #8393.
The result is as follows - note how all properties ending in XOXO, across all levels of the hierarchy, were removed:
{
"Id": 1,
"Name": "Pablo",
"Things": [
{
"Name": "Something"
}
]
}
Remove-Property source code
Important: Remove-Property:
assumes that the input objects are custom objects ([pscustomobject]), such as created by ConvertFrom-Json.
it modifies these objects in place, in addition to outputting the modified object; this differs from Select-Object, which creates new objects from the input.
function Remove-Property {
param(
[Parameter(Mandatory, ValueFromPipeline, Position = 0)]
[object] $InputObject,
[Parameter(Mandatory, Position = 1)]
[string] $NamePattern
)
process {
foreach ($el in $InputObject) {
foreach ($propName in $el.psobject.Properties.Name) {
if ($propName -like $NamePattern) {
$el.psobject.Properties.Remove($propName)
}
else {
$null = Remove-Property -InputObject $el.$propName -NamePattern $NamePattern
}
}
}
$InputObject
}
}
I don't prefer this solution, but it does seem easier than recursively traversing an object's nested properties of unknown depths.
$json = #'
{
"Id": 1,
"Name": "Pablo",
"UnwantedProperty1XOXO": true,
"UnwantedProperty2XOXO": false,
"Things": [
{
"Name": "Something",
"UnwantedProperty3XOXO": true
}
]
}
'#
$filter = "XOXO"
$json -replace ".*$filter.*\r?\n" -replace ",(?=\r?\n\W+})" | ConvertFrom-Json
Maybe this will work.
filter Remove-Property ($Name) {
$queue = [Collections.Generic.Queue[object]]::new(#(Get-Variable _))
while ($queue.Count) {
foreach ($elem in $queue.Dequeue().Value) {
$props = $elem.psobject.Properties
foreach ($p in $props) {
if ($p.Name -like $Name) { $props.Remove($p.Name) } else { $queue.Enqueue($p) }
}
}
}
}
The usage is as follows.
$myObject | Remove-Property -Name "*XOXO"
The ConvertFrom-Json Cmdlet by default has a depth of 2. This is most likely causing your issue.
To fix, use this ConvertFrom-Json command:
ConvertFrom-Json $input -Depth 10
Reference: ConvertFrom-Json

Using a variables string value in variable name

It should work like:
$part = 'able'
$variable = 5
Write-Host $vari$($part)
And this should print "5", since that's the value of $variable.
I want to use this to call a method on several variables that have similar, but not identical names without using a switch-statement. It would be enough if I can call the variable using something similar to:
New-Variable -Name "something"
but for calling the variable, not setting it.
Editing to add a concrete example of what I'm doing:
Switch($SearchType) {
'User'{
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJUsers_ListBox.Items.Add($Item)
}
}
'Computer' {
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJComputers_ListBox.Items.Add($Item)
}
}
'Group' {
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJGroups_ListBox.Items.Add($Item)
}
}
}
I want this to look like:
ForEach($Item in $OBJResults_ListBox.SelectedItems) {
$OBJ$($SearchType)s_ListBox.Items.Add($Item)
}
You're looking for Get-Variable -ValueOnly:
Write-Host $(Get-Variable "vari$part" -ValueOnly)
Instead of calling Get-Variable every single time you need to resolve a ListBox reference, you could pre-propulate a hashtable based on the partial names and use that instead:
# Do this once, just before launching the GUI:
$ListBoxTable = #{}
Get-Variable OBJ*_ListBox|%{
$ListBoxTable[($_.Name -replace '^OBJ(.*)_ListBox$','$1')] = $_.Value
}
# Replace your switch with this
foreach($Item in $OBJResults_ListBox.SelectedItems) {
$ListBoxTable[$SearchType].Items.Add($Item)
}

Parsing PowerShell script with AST

I'm trying to parse through a Pester script and extract values from the -Tag parameter. Anyone know how to do this using [System.Management.Automation.PSParser]?. I'm was thinking I'd have to loop through the tokens returned from [System.Management.Automation.PSParser]::Tokenize() but that seems pretty kludgy and given that the values for -Tag could be given in many different formats, not very practical.
At the end of the day, I'm hoping to return a collection with the Describe block name, and the list of tags (if any) for that block.
Name Tags
---- ----
Section1 {tag1, tag2}
Section2 {foo, bar}
Section3 {asdf}
Section4 {}
Here are the sample Pester tests that I'm working with.
describe 'Section1' -Tag #('tag1', 'tag2') {
it 'blah1' {
$true | should be $true
}
}
describe 'Section2' -Tag 'foo', 'bar' {
it 'blah2' {
$true | should be $true
}
}
describe 'Section3' -Tag 'asdf'{
it 'blah3' {
$true | should be $true
}
}
describe 'Section4' {
it 'blah4' {
$true | should be $true
}
}
Anyone have any ideas on how to solve this? Is [System.Management.Automation.PSParser] the right way to go or is there a better way?
Cheers
Using PS3.0+ Language namespace AST parser:
$text = Get-Content 'pester-script.ps1' -Raw # text is a multiline string, not an array!
$tokens = $null
$errors = $null
[Management.Automation.Language.Parser]::ParseInput($text, [ref]$tokens, [ref]$errors).
FindAll([Func[Management.Automation.Language.Ast,bool]]{
param ($ast)
$ast.CommandElements -and
$ast.CommandElements[0].Value -eq 'describe'
}, $true) |
ForEach {
$CE = $_.CommandElements
$secondString = ($CE | Where { $_.StaticType.name -eq 'string' })[1]
$tagIdx = $CE.IndexOf(($CE | Where ParameterName -eq 'Tag')) + 1
$tags = if ($tagIdx -and $tagIdx -lt $CE.Count) {
$CE[$tagIdx].Extent
}
New-Object PSCustomObject -Property #{
Name = $secondString
Tags = $tags
}
}
Name Tags
---- ----
'Section1' #('tag1', 'tag2')
'Section2' 'foo', 'bar'
'Section3' 'asdf'
'Section4'
The code doesn't interpret the tags as a list of strings, but simply uses the original text extent.
Use the debugger in PowerShell ISE / Visual Studio / VSCode to inspect the various data type cases.