How do I access object properties in a dynamic manner? - powershell

I have, what in my opinion is, bad-looking code, as i currently update properties on a case-by-case basis through the use of a switch statement. Instead, I would like to dynamically update a property if a $Key by the same name as the property can be found in $PSBoundParameters. Note that each given $Key is assumed to also exist as a property in the object InputObject.
My current solution:
foreach ($Key in $PSBoundParameters.Keys) {
switch ($Key) {
{ $_ -Match "^TimeToLive$"} { $InputObject.RecordData.TimeToLive = $PSBoundParameters[$Key] }
{ $_ -Match "^AllowUpdateAny$"} { $InputObject.RecordData.AllowUpdateAny = $PSBoundParameters[$Key] }
{ $_ -Match "^IPv4Address$"} { $InputObject.RecordData.IPv4Address = $PSBoundParameters[$Key] }
{ $_ -Match "^IPv6Address$"} { $InputObject.RecordData.IPv6Address = $PSBoundParameters[$Key] }
{ $_ -Match "^HostNameAlias$"} { $InputObject.RecordData.HostNameAlias = $PSBoundParameters[$Key] }
{ $_ -Match "^PtrDomainName$"} { $InputObject.RecordData.PtrDomainName = $PSBoundParameters[$Key] }
{ $_ -Match "^MailExchange$"} { $InputObject.RecordData.MailExchange = $PSBoundParameters[$Key] }
{ $_ -Match "^Preference$"} { $InputObject.RecordData.Preference = $PSBoundParameters[$Key] }
{ $_ -Match "^DomainName$"} { $InputObject.RecordData.DomainName = $PSBoundParameters[$Key] }
{ $_ -Match "^Priority$"} { $InputObject.RecordData.Priority = $PSBoundParameters[$Key] }
{ $_ -Match "^Weight$"} { $InputObject.RecordData.Weight = $PSBoundParameters[$Key] }
{ $_ -Match "^Port$"} { $InputObject.RecordData.Port = $PSBoundParameters[$Key] }
}
}
Pseudocode for what I want my solution to look like:
For each $Key in $PSBoundParameters
Set $InputObject.RecordData property of name $Key to value of current key / value pair
Any improvement to my current solution is much appreciated. Thank you.

You'll need a list of parameter names to filter against, at which point you can simplify your loop to:
$RecordDataPropertyNames = 'TimeToLive', 'AllowUpdateAny', 'IPv4Address', 'IPv6Address', 'HostNameAlias', 'PtrDomainName', 'MailExchange', 'Preference', 'DomainName', 'Priority', 'Weight', 'Port'
# ...
foreach($key in $PSBoundParameters.Keys |Where {$_ -in $RecordDataPropertyNames}){
$InputObject.RecordData.$key = $PSBoundParameters[$key]
}

Related

CSV multiple filters

Im importing a csv and have multiple filters that the end user can select to filter the csv.
foreach filter Im += to an array.
Problem I run into is-
If a user selects 'Medium' and USD, they should only be able to see Rows of data which has both 'Medium' from the impact column and USD from country.
My results are returning 'Medium' from any country AND 'USD' events?
I feel like Im going about this the wrong way. Any ideas?
Thank you for your time.
$buttonFilter_Click={
$r = #()
#Filter Impact Column#
if ($checkboxLow.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Impact -eq 'Low' }
}
if ($checkboxMedium.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Impact -eq 'Medium' }
}
if ($checkboxHigh.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Impact -eq 'High' }
}
#Filter Country column#
if ($checkboxUSD.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'USD' }
}
if ($checkboxCHF.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'CHF' }
}
if ($checkboxGBP.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'GBP' }
}
if ($checkboxAUD.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'AUD' }
}
if ($checkboxCNY.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'CNY' }
}
if ($checkboxJPY.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'JPY' }
}
if ($checkboxCAD.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'CAD' }
}
if ($checkboxEUR.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'EUR' }
}
if ($checkboxNZD.Checked -eq $true)
{
$r += $script:WorldWideNews | ? { $_.Country -eq 'NZD' }
}
$table = ConvertTo-DataTable -InputObject $r
Update-DataGridView -DataGridView $datagridviewUpcomingNews -Item $table
}
Your code forms the union of all filter results, along both dimensions, impact and country (currency).
Instead, you need the intersection of the results from these two dimensions.
The immediate fix is to collect an intermediate result for the filtering performed along the first dimension, and then base the filtering along the second dimension on that:
$r = #()
#Filter Impact Column#
if ($checkboxLow.Checked)
{
$r += $script:WorldWideNews | ? { $_.Impact -eq 'Low' }
}
# ...
# Save the results of the impact filtering
# and base all subsequent filtering on that.
$impactFilterResults = $r
$r = #()
#Filter Country column# - note the use of $impactFilterResults as input.
if ($checkboxUSD.Checked)
{
$r += $impactFilterResults | ? { $_.Country -eq 'USD' }
}
if ($checkboxCHF.Checked)
{
$r += $impactFilterResults | ? { $_.Country -eq 'CHF' }
}
# ...
Note, however, that your code can be streamlined, by ultimately combining all filters into a composite one that a single Where-Object (?) call could perform:
$buttonFilter_Click = {
# Map the checkboxes to the corresponding values to filter by.
$impactValueMap = #{
$checkboxLow = 'Low'
$checkboxMedium = 'Medium'
$checkboxHigh = 'High'
}
# Based on which are checked, determine the list of values to filter by.
[array] $impactsToFilterBy = foreach ($checkbox in $checkboxLow, $checkboxMedium, $checkboxHigh) {
if ($checkbox.Checked) { $impactValueMap[$checkbox] }
}
# Note: Truncated to 3 entries for brevity
$countryValueMap = #{
$checkboxUSD = 'USD'
$checkboxCHF = 'CHF'
$checkboxGBP = 'GBP'
# ...
}
[array] $countriesToFilterBy = foreach ($checkbox in $checkboxUSD, $checkboxCHF, $checkboxGBP) {
if ($checkbox.Checked) { $countryValueMap[$checkbox] }
}
# Use a single Where-Object call to filter, based
# on *arrays* of values to match.
$r =
$script:WorldWideNews |
Where-Object { $_.Impact -in $impactsToFilterBy -and $_.Country -in $countriesToFilterBy }
$table = ConvertTo-DataTable -InputObject $r
Update-DataGridView -DataGridView $datagridviewUpcomingNews -Item $table
}

How can I check if a 2 numbers have the same digits PowerShell?

I want to compare two int so if they contain the same digits it outputs a true,for example:
$a=1260
$b=2106
and then because both of them contain: 0126 it outputs true how can this be made?
And if it's possible with the fewest possible lines
Here's one technique:
$null -eq (Compare-Object -ReferenceObject ([char[]][String]1260) -DifferenceObject ([char[]][String]2601))
Which returns true or false, depending on if the digits are the same or not.
Here is another, a bit lengthier solution:
( $a.ToString().ToCharArray() | ForEach-Object { $c = $true } { if ( $b.ToString() -notmatch $_.ToString() ) { $c = $false } } { $c } ) -and ( $b.ToString().ToCharArray() | ForEach-Object { $c = $true } { if ( $a.ToString() -notmatch $_.ToString() ) { $c = $false } } { $c } )
This compares the int's as arrays and thus need to be run bothways.

Powershell Return only TRUE if All Values are the same

I have the script below to read registry values from a certain key(taking no credit for it). My end goal is to only return TRUE if all the values in the array Match. However I'm not quite getting it as
Example Registry Entry
$array = #()
$regval = Get-Item -Path HKLM:\SOFTWARE\Runner\Event
$regval.GetValueNames() |
ForEach-Object {
$name = $_
$rv.Value
$array += New-Object psobject -Property #{'Value' = $rv.Value }
}
$Matchvalue = 'A'
Foreach ($v in $array){
if ($v -match $Matchvalue){
$true
}
}
Update: I've just tried again and it appears my array is empty. So any tips welcome for me.
How about this:
$regkey = Get-Item HKLM:\SOFTWARE\Runner\Event
$matchPattern = 'A'
$values = $regkey.GetValueNames()
$matchingValues = $values | Where { $regkey.GetValue($_) -match $matchPattern }
# this is going to be true or false
$values.Count -eq $matchingValues.Count
Note that by default, Powershell is case-insensitive. So $matchPattern = 'A' and $matchPattern = 'a' will behave the same.
Here's my attempt to do something like Haskell's "all".
function foldr ($sb, $accum, $list) {
if ($list.count -eq 0) { $accum }
else { & $sb $list[0] (foldr $sb $accum $list[1..$list.length]) }
}
function and ($list) {
foldr { $args[0] -and $args[1] } $true $list
}
function all ($list, $sb) {
and ( $list | foreach $sb )
}
all 1,1,1 { $_ -eq 1 }
True
all 1,2,1 { $_ -eq 1 }
False

How to output return value using Powershell?

I want to output the return of this process. Anyone can help me please. Thank you.
$name = $false
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } Out-File .\PN.txt}
}
I tried that way, but the output file is empty.
The Out-File .\PN.txt command is only ever reached for (a) lines that start with a non-whitespace character (\S) while (b) $name isn't $true.
When it is reached, it creates an empty .\PN.txt file, due to lack of input.
If, perhaps, your intent was to send all output from the switch statement to a file, try the following:
$name = $false
& {
switch -regex -file .\bios.txt {
'^Product Name' { $name = $true; continue }
'^\s' { if ($name) { $_.Trim() }}
'^\S' { if ($name) { return } $_ }
}
} | Out-File .\PN.txt

Return from function

I wish to return from the following powershell function if I find a match (for a more complete code sample see my codereview question):
Function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)){ return $_ }
}
}
This code just appears to return from the scope of the if conditional, which is not so useful.
How can I do this? Do I need a labeled break somewhere?
If you wish to use the return statement to exit the function you can use the foreach keyword instead of the ForEach-Object cmdlet. Here's a demo:
function Foo {
foreach ($number in (0..287)) {
$number # Just show our current iteration.
if ($number -eq 50) {
return $number
}
}
}
No need for a label.
function Find-Property($Filename, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Where {$PropertyName -eq $shellfolder.GetDetailsOf($null, $_)} |
Foreach {$_;break}
}
Another option is to minorly tweak your original function:
function Find-Property($fileName, $PropertyName)
{
$shellfolder = Create-ShellFolder $fileName
0..287 | Foreach-Object {
if($PropertyName -eq $shellfolder.GetDetailsOf($null, $_)) {$_; break}
}
}