How to set a custom objects attribute with Powershell? - powershell

How do I add String data to the array of the class OnePerson in order to group the data?
$people = import-csv "./people.csv"
class OnePerson {
[String] $Info
people () { }
}
$myPerson = New-Object -TypeName OnePerson
$manyPeople = New-Object System.Object
$myArray = #()
ForEach ($person in $people) {
if ($person -match '[0-9]') {
Write-host $person
#add $person string to the array $Info of $myPerson
}
else {
write-host "new person"
write-host $person
$myArray += $myPerson
$myPerson = New-Object -TypeName OnePerson
}
}
write-host $myArray
output:
thufir#dur:~/flwor/people$
thufir#dur:~/flwor/people$ pwsh foo.ps1
new person
#{people=joe}
#{people=phone1}
#{people=phone2}
#{people=phone3}
new person
#{people=sue}
#{people=cell4}
#{people=home5}
new person
#{people=alice}
#{people=atrib6}
#{people=x7}
#{people=y9}
#{people=z10}
OnePerson OnePerson OnePerson
thufir#dur:~/flwor/people$

Example on how you can use OnePerson class and adding that element to your array is,
class OnePerson {
[String] $Info
OnePerson () { }
OnePerson ([String]$newinfo) { $this.Info = $newInfo }
}
$myArray = #()
$myArray += [OnePerson]::new("John")
$myArray += [OnePerson]::new("Smith")
Constructors you use in class have to have the same name as your class itself. Once the person has been created and added to myArray, it no longer exists on it's own, only available via reference from myArray

Related

Powershell list of objects of a class type

I am trying to iterate through a list of objects of a class type.
The class looks like this :
Class UrlTestModel
{
[String]$Name
[String]$Url
[String]$TestInProd
}
So I would like to be able to create a list of objects that contain the three strings above, then iterate through the list and do stuff with the data.
For some reason I cant see a way to do that. Seems simple enough. I am kinda new to powershell, and come from a C# background, so I might be thinking too C# and not enough powershell. :)
You can create a new instance this way:
New-Object -TypeName "UrlTestModel" -Property #{
Name = "string"
Url = "string"
TestInProd = "string"
}
Or define a constructor in your class:
class UrlTestModel
{
[String]$Name
[String]$Url
[String]$TestInProd
UrlTestModel([string]$name, [string]$url, [string]$testInProd) {
$this.Name = $name
$this.Url = $url
$this.TestInProd = $testInProd
}
}
And then create a new instance like this:
[UrlTestModel]::new("string", "string", "string")
You can read more about it in about_Classes.
Lists are basically created by using the comma operator:
$list = [UrlTestModel]::new("name1", "url1", "true"), [UrlTestModel]::new("name2", "url2", "false")
# or
$a = New-Object -TypeName UrlTestModel -Property #{Name = "string"; Url = "string"; TestInProd = "string" }
$b = New-Object -TypeName UrlTestModel -Property #{Name = "string"; Url = "string"; TestInProd = "string" }
$list = $a, $b
Iterate over a list using the ForEach-Object cmdlet or the foreach statement:
$list | ForEach-Object {
# this is the current item:
$_
}
# or
foreach ($item in $list) {
# ...
}

Find duplicates in array of hashtables

I have an array of hashtables and I need to find if there are elements who has the same Name.
I have this HasDuplicate function which return True or False if the array contains duplicate or not.
What I am doing here is that I am iterating through each element and add Name of it to another array, and then check it if it exists. But this code does not looks good, and I was thinking if there is another way of achieving this
# object looks like this
$array = #(
#{ Name = 'First', Passed = $True }
#{ Name = 'First', Passed = $False }
)
Function HasDuplicate
{
param($array)
$all = #()
foreach($item in $array)
{
$item_name = $item.Name
if($all -contains $item_name)
{
Write-Error "Duplicate name ""$item_name"""
return $True
}
else
{
$all += $item_name
}
}
return $False
}
Group-Object is probably the easiet, something like this:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$array.Name | Group-Object | Where-Object Count -GT 1
Another way you could do it using an hash table:
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
)
$h = #{}
$array | % {$h[$_.Name] += 1 }
$h.GetEnumerator() | Where value -GT 1
This might not be very good looking compared to the other answers, but you could just count your names in another hashtable then output the duplicates afterwards.
$array = #(
#{ Name = 'First'; Passed = $True }
#{ Name = 'First'; Passed = $False }
);
# Count names in array
$counts = #{}
foreach ($object in $array) {
$name = $object.Name
if (-not $counts.ContainsKey($name)) {
$counts[$name] = 0
}
$counts[$name] += 1
}
# Output duplicates
foreach ($name in $counts.Keys) {
if ($counts[$name] -gt 1) {
Write-Output ("Duplicate Name: " + $name)
}
}
Output:
Duplicate Name: First

Powershell: Property stored in a variable

I would like to find all cells in a range based on a property value using EPPlus. Let's say I need to find all cells with bold text in an existing spreadsheet. I need to create a function that will accept a configurable properties parameter but I'm having trouble using a property stored in a variable:
$cellobject = $ws.cells[1,1,10,10]
$properties = 'Style.Font.Bold'
$cellobject.$properties
$cellobject.{$properties}
$cellobject.($properties)
$cellobject."$properties"
None of these work and cause a call depth overflow.
If this way wont work, is there something in the library I can use?
Edited: To show the final solution I updated the function with concepts provided by HanShotFirst...
function Get-CellObject($ExcelSheet,[string]$PropertyString,[regex]$Value){
#First you have to get the last row with text,
#solution for that is not provided here...
$Row = Get-LastUsedRow -ExcelSheet $ExcelSheet -Dimension $true
while($Row -gt 0){
$range = $ExcelSheet.Cells[$Row, 1, $Row, $ExcelSheet.Dimension.End.Column]
foreach($cellObject in $range){
if($PropertyString -like '*.*'){
$PropertyArr = $PropertyString.Split('.')
$thisObject = $cellObject
foreach($Property in $PropertyArr){
$thisObject = $thisObject.$Property
if($thisObject -match $Value){
$cellObject
}
}
}
else{
if($cellObject.$PropertyString -match $Value){
$cellObject
}
}
}
$Row--
}
}
#The ExcelSheet parameter takes a worksheet object
Get-CellObject -ExcelSheet $ws -Property 'Style.Font.Bold' -Value 'True'
Dot walking into properties does not really work with a string. You need to separate the layers of properties. Here is an example for an object with three layers of properties.
# create object
$props = #{
first = #{
second = #{
third = 'test'
}
}
}
$obj = New-Object -TypeName psobject -Property $props
# outputs "test"
$obj.first.second.third
# does not work
$obj.'first.second.third'
# outputs "test"
$a = 'first'
$b = 'second'
$c = 'third'
$obj.$a.$b.$c
In your example this would be something like this:
$cellobject = $ws.cells[1,1,10,10]
$p1 = 'Style'
$p2 = 'Font'
$p3 = 'Bold'
$cellobject.$p1.$p2.$p3
Or you can do it a bit dynamic. This should produce the same result:
$cellobject = $ws.cells[1,1,10,10]
$props = 'Style.Font.Bold'.Split('.')
$result = $cellobject
foreach ($prop in $props) {
$result = $result.$prop
}
$result
And since its Friday, here is a function for it :)
function GetValue {
param (
[psobject]$InputObject,
[string]$PropertyString
)
if ($PropertyString -like '*.*') {
$props = $PropertyString.Split('.')
$result = $InputObject
foreach ($prop in $props) {
$result = $result.$prop
}
} else {
$result = $InputObject.$PropertyString
}
$result
}
# then call the function
GetValue -InputObject $cellobject -PropertyString 'Style.Font.Bold'

Powershell Classes

I have used closures in Powershell to create classes with static and instance methods. Is there a better way to do this?
Create and object with a static "method".
function person_get_type {
return 'Person'
}
function make_person {
param
(
[parameter(mandatory=$true)][string]$name)
$properties = #{'name'=$name;'get_type'=$null}
$person = New-Object –TypeName PSObject –Prop $properties
$person.get_type = ${function:person_get_type}
return $person
}
$person = make_person -name 'James'
$type = $person.get_type.InvokeReturnAsIs()
Write-Host $type
Create an object with an instance "method".
function dog_get_type {
return 'Dog'
}
function dog_make_get_name{
param
(
[Parameter(Mandatory=$true)][System.Management.Automation.PSObject]$self
)
return {return $self.name}.GetNewClosure()
}
function dog_make_set_name{
param
(
[Parameter(Mandatory=$true)][System.Management.Automation.PSObject]$self
)
return {param([parameter(mandatory=$true)][string]$name) $self.name = $name}.GetNewClosure()
}
function make_dog {
param
(
[parameter(mandatory=$true)][string]$name
)
$properties = #{'name'=$name;'get_type'=$null;'get_name'=$null;'set_name'=$null}
$dog = New-Object –TypeName PSObject –Prop $properties
$dog.get_type = ${function:dog_get_type}
$dog.get_name = dog_make_get_name -self $dog
$dog.set_name = dog_make_set_name -self $dog
return $dog
}
$dog = make_dog -name 'Spot'
$name = $dog.get_name.InvokeReturnAsIs()
Write-Host $name
$dog.set_name.Invoke("Paws")
$name = $dog.get_name.InvokeReturnAsIs()
Write-Host $name
$stuff = #($person,$dog)
foreach($thing in $stuff) {
Write-Host $thing.get_type.InvokeReturnAsIs()
}
I've see where it's possible to use this approach:
$object = New-Module -AsCustomObject -ScriptBlock {...}
But I don't see that's possible to create instance methods using this approach.
Instance methods should be easy using New-Module. Your instance fields are the top level variables in the scriptblock e.g.:
$sb = {
param($theName,$theAge,$theBreed)
$Name = $theName
$Age = $theAge
$Breed = $theBreed
$global:NumDogs++
function Description {
"Dog named $Name, age $Age, breed $Breed"
}
function get_NumDogs {
"Total number of dogs is $NumDogs"
}
Export-ModuleMember -Variable Name,Age,Breed -Function Description,get_NumDogs
}
$dog1 = New-Module $sb -AsCustomObject -ArgumentList 'Mr. Bill',1,'Jack Russell'
$dog1.Name
$dog1.Age
$dog1.Description()
$dog1.get_NumDogs()
$dog2 = New-Module $sb -AsCustomObject -ArgumentList Fluffy,3,Poodle
$dog2.Name
$dog2.Age
$dog2.Description()
$dog2.get_NumDogs()

Set Object Property by Reference

I have a collection of PSObjects over which I'd like to iterate, setting contituent member's properties. I set up a for loop and pass the current object by reference to a function, but do not know how to access the object properties. Example:
function create-object {
$foo = new-object -TypeName PSObject -Prop `
#{
"p1" = $null
"p2" = $null
}
$foo
}
$objCol = #()
foreach ($k in (1 .. 3)){$objCol += create-object}
for ($i=0;$i -le $objCol.Length;$i++) {
Write-Host "hi"
reftest ([ref]$objCol[$i])
}
function reftest([ref]$input)
{
$input.p1.value="property1"
}
$objCol
... returns Property 'p1' cannot be found on this object --how do I set $object.p1 from a function by reference?
I've reformatted your example, also incorporating the change of $input to a different name, $arg, as pointed out by Christian. The following works:
function create-object {
$foo = new-object PSObject -Property #{
"p1" = $null
"p2" = $null
}
$foo
}
function reftest($arg)
{
$arg.p1="property1"
}
$objCol = #()
(1..3) | % {$objCol += create-object}
for ($i=0;$i -lt $objCol.Length;$i++) {
reftest $objCol[$i]
}
$objCol