Why are the indices of the arraylist added to the arraylist itself? - arrays

I'm writing a script that dynamically generates to the screen a list of AD sites that happen to contain Exchange servers (specifically, using Get-ExchangeServer | % {Get-ADSite $_.Site} | Sort -Unique) and that part works well enough when I generate that list like so:
function SiteChoiceMenu {
$sites = Get-ExchangeServer | % {Get-ADSite $_.Site} | Sort -Unique
$siteCount = $sites.count
$bullets = New-Object -TypeName System.Collections.ArrayList($null)
for ($i=0;$i -lt $siteCount;$i++) {
$index = $i+1
$bullet = "$index. " + $sites[$i].Name
$bullets.Add($bullet)
}
foreach ($bullet in $bullets) {Write-Host $bullet}
$siteChoice = Read-Host "Enter choice"
if ($siteChoice -in 1..$siteCount) { $siteChosen = $sites[$siteChoice-1]; return $siteChosen }
}
$siteChosen
When I run this code, I have 5 sites that have Exchange servers, so it generates a list like this:
1. Site 1
2. Site 2
and so forth. When I write $siteChosen to the screen, the output looks like this:
0
1
2
3
4
5
Name HubSiteEnabled
Site1 False
When I step into the code, I can see that "siteChosen" is set to the exact output above. When I look at my Arraylist, $bullets, it shows only the strings I expect it to show. Now, what I did try is using a normal array instead of an ArrayList collection object, it does not exhibit the behavior above. When I write out $siteChosen to the screen, it has just the site in it. Any ideas why?

$bullets.Add($bullet) returns the the index at which the object was added in the ArrayList. Because you don't store it in a variable, the returned int is accumulated in the pipeline.
$thorwaway = $bullets.Add($bullet) will hide it.
This is just one of those things you have to watch out for when using .NET objects and methods.

Related

How do I update an array in an array in a hashtable in Powershell?

I'm using Powershell desired state configuration and have a separate definition document (psd1) I load into memory via
$ConfigData = Import-PowerShellDataFile "$Path"
where I'd like to update the node names based on an environment variable. The overall psd1 file shows it's type as a hashtable (name and basetype are below), then allnodes shows up as an array, then nodename as another array.
Hashtable System.Object
Object[] System.Array
Object[] System.Array
Replace doesn't persist (like below). If I try to assign it back to itself or a copy, 'The property 'nodename' cannot be found on this object. Verify that the property exists yadda'
$ConfigData.AllNodes.NodeName -replace 'vms','vmp'
or
$ConfigDataHolder.AllNodes.NodeName = $ConfigData.AllNodes.NodeName -replace 'vms','vmp'
Direct reference/assigment doesn't persist, where below's output is still the servername previously, even in a clone scenario.
$ConfigData.AllNodes.NodeName[2] = "something"
Since you're using property access on arrays in order to access values of its elements, you're taking advantage of member-access enumeration.
However, member-access enumeration - by design - only works for getting values, not for setting (updating) them.
The behavior when updating is attempted is obscure, unfortunately, as you've experienced:
When you try to assign to a member-access-enumerated property as a whole, the error message is obscure: it tells you that no such property exists, because on setting it only looks at the array object itself, even though it does find it on getting, when it looks at the arrays elements; a minimal example:
$data = #{ arr = #(#{ subarr = 1, 2 }, #{ subarr = 3, 4 }) }
# !! ERROR " property 'subarr' cannot be found on this object"
$data.arr.subarr = #(42, 43)
If you try a specific index (e.g. [2]), but the array that the index is applied to was itself obtained via member-access enumeration, the assignment in effect operates on a temporary array, and is therefore effectively discarded, quietly; a minimal example:
$data = #{ arr = #(#{ subarr = 1, 2 }, #{ subarr = 3, 4 }) }
# !! IGNORED, because .arr.subarr is the result of
# !! member-access enumeration.
$data.arr.subarr[0] = 42
The solution is to target the array elements individually for updating, either with a single index (e.g., [2]) or in a loop, one by one.
A simplified example:
$configData = #{
AllNodes = #(
#{
NodeName = #(
'Node1a',
'Node1b'
)
},
#{
NodeName = #(
'Node2a',
'Node2b'
)
}
)
}
# OK: a specific element of *both* arrays involved - .AllNodes and .NodeName -
# is targeted and can therefore be assigned to.
$configData.AllNodes[0].NodeName[1] = 'NewNode1b'

jq split array into two arrays

I am new to jq and still trying to learn the basics of JSON, so please, excuse my lack of knowledge.
I find the tool amazingly fast, but I am struggling to get the results I need. I am sure it's possible as I've made it once by accident :(
I have input data, such as
[
{"time":1499150456,"data":{"power":{"bus":3.88,"shunt":6.98,"load":3.89,"current":76.00},"light":{"light":21}}},
{"time":1499150516,"data":{"power":{"bus":3.93,"shunt":1.67,"load":3.93,"current":16.20},"light":{"light":21}}},
{"time":1499150576,"data":{"power":{"bus":3.92,"shunt":5.58,"load":3.93,"current":25.30},"light":{"light":21}}},
{}
]
I want to extract it into something like
[
1499150456,
1499150516,
1499150576
]
[
76.00,
16.20,
25.30
]
What I used so far was:
cat inputFile.json | jq -C '.[] | select (length > 0)'
which outputs nice initial array without the last empty record.
Next, I am able to do
cat inputFile.json | jq -C '.[] | select (length > 0) | .time, .data.power.current'
The result is very close, but not exactly what I need.
I wanted to use map(.time) that I found in some example, but that resulted in an error - not sure how to use it and the examples did not work for me so far.
The following filter produces the output exactly as described:
map(select(.time))
| map(.time), map(.data.power.current)
This is parsed as map(...) | ( map(...), map(...) ), thus resulting in a stream consisting of two JSON arrays.
first of all you can create a new json object. Before creting operation you should collect data in new array like this;
var timeData=[];
var currentData=[];
var mergedJsonData={};
var yourJsonDataLenght= JSON.yourJsonData.length;
for (infoIndex = 0; infoIndex < yourJsonDataLenght; infoIndex++) {
timeData.push(JSON.yourJsonData.time[infoIndex]);
currentData.push(JSON.yourJsonData.time[infoIndex].power.current);
}
mergedJsonData.push(timeData);
mergedJsonData.push(currentData);

Finding out what is in an array of Hash (on a server)

I am using a 3rd party shopping cart solution that runs on a server (SellerDeck). I have some code that runs on the server to format a shopping basket with basic product data (quantity, price, name). I need to find some more data which I think is held in 2 arrays of hashes. I dont know what is contained in these 2 arrays so I would like to convert the array to a string and output via the existing code that puts it in a cookie on the client which I can then view. The 2 arrays are $pCartItem and $pProduct (see code at the bottom for how they are used).
The string $cartStr (bottom of the code) is output onto the client in a cookie by another part of the code.
I would like to covert the 2 arrays into 2 strings which can be concatenated onto $cartStr. I can then read the contents on my local pc (client). My issue is I am very unfamiliar with perl and know how to do the conversion.
I tried adding :
my $MiniCrtS=" ";
my $MiniCartElmt;
foreach $MiniCartElmt (#{$pProduct}) {
$MiniCrtS=$MiniCrtS . $MiniCartElmt;
}
and then changed the $cartStr from:
HTML::Entities::encode(substr($pProduct->{'NAME'},0,$abrv))
to:
HTML::Entities::encode(substr($MiniCrtS,0,$abrv))
but this change makes the code crash when run.
Any ideas on what I am doing wrong or an alternative to find out the data in the arrays?
Many thanks Tony
The relevant code is:
sub miniCart
{
use HTML::Entities ();
my $Self = shift;
my $abrv=12; # number of characters to abbreviate item name
my $defaultCur="£"; # currency symbol to include
my $cartStr="ss=" . $::g_sSearchScript . "cur=" . $defaultCur;
my $pCartItem;
foreach $pCartItem (#{$Self->{_CartList}})
{
my ($Status, $Message, $pProduct) = GetProduct($pCartItem->{'PRODUCT_REFERENCE'}, $pCartItem->{'SID'});
if ($Status == $::FAILURE)
{
return ($Status, $Message, []);
}
elsif ($Status == $::NOTFOUND)
{
next;
}
my #Prices = $Self->GetCartItemPrice($pCartItem);
$cartStr=$cartStr . "&!" . $pCartItem->{'QUANTITY'} . "x" . HTML::Entities::encode($pCartItem->{'PRODUCT_REFERENCE'}) . ">" . HTML::Entities::encode(substr($pProduct->{'NAME'},0,$abrv)) . ">" . $Prices[2]/100;
}
return $cartStr;
}
To get a dump of a data structure, you can use Data::Dumper.
I'd use it as follows:
use Data::Dumper qw( );
sub dumper {
local $Data::Dumper::Indent = 0;
local $Data::Dumper::Sortkeys = 1;
local $Data::Dumper::Terse = 1;
local $Data::Dumper::Useqq = 1;
return Data::Dumper::Dumper($_[0]);
}
warn("pProduct: " . dumper($pProduct));
This will log it the string produced to the web server's error log, but you could also return it in a cookie if that's what you really want.

Perl - Search array of words to check for a partial match in any part of an email address

Again, a whole day and I am stuck again.
I need to use an array of words or var that contains forbidden words that cannot appear in an email address.
Either:
$baddies = 'smtp mailer sysop';
or
#baddies = qw(smtp mailer sysop);
or
#baddies = qw/smtp mailer sysop/;
There are more bad words in the array too, about two dozen.
I am not running the latest version of perl so ~~ and so on are not supported.
I have a loop going on that sends the bands schedule out.
In that loop I need to check to see if the email contains any of those words.
I realize there may be some good emails that contain a match but, that is fine.
I have tried literally dozens of examples after I gave up trying to figure it out.
Latest was:
####FYI## $uaddress is from the foreach $uaddress(#addresses){ loop.
my %params = map { $uaddress => 1 } #baddies;
if(exists($params{$uaddress})) {
print "yep, it's there"; #for testing
push(#failed,"$uaddress is restricted<br />");
But, everything I tried just does not do what I need.
I even tried =~ and so on.
I AM REALLY feeling stupid about now..
I need another lesson here folks.. Thanks in advance.
Update: I also tried:
$baddies = 'smtp mailer sysop';
my #baddies = split / /, $baddies;
# iterate through the array
foreach (#baddies) {
if($_ =~ $uaddress) #I also reversed that {
print qq~$uaddress contains $_~;
}
}
Through trial, error and dumb luck, I haphazardly got this below to work:
foreach $uaddress(#addressList) {
$uaddress =~ s/\s//g;
#baddies = qw(smtp mailer sysop);
my #failed;
foreach (#baddies) {
if($uaddress =~ m/$_/i) {
push(#failed,"$uaddress is restricted because it found \"$_\" in the address<br />");
}
}
##stuff happens with emails that passed like Email::Verify
}

How do I consolidate a hash in Perl?

I have an array of hash references. The hashes contain 2 keys, USER and PAGES. The goal here is to go through the array of hash references and keep a running total of the pages that the user printed on a printer (this comes from the event logs). I pulled the data from an Excel spreadsheet and used regexes to pull the username and pages. There are 182 rows in the spreadsheet and each row contains a username and the number of pages they printed on that job. Currently the script can print each print job (all 182) with the username and the pages they printed but I want to consolidate this down so it will show: username 266 (i.e. just show the username once, and the total number of pages they printed for the whole spreadsheet.
Here is my attempt at going through the array of hash references, seeing if the user already exists and if so, += the number of pages for that user into a new array of hash references (a smaller one). If not, then add the user to the new hash ref array:
my $criteria = "USER";
my #sorted_users = sort { $a->{$criteria} cmp $b->{$criteria} } #user_array_of_hash_refs;
my #hash_ref_arr;
my $hash_ref = \#hash_ref_arr;
foreach my $index (#sorted_users)
{
my %hash = (USER=>"",PAGES=>"");
if(exists $index{$index->{USER}})
{
$hash{PAGES}+=$index->{PAGES};
}
else
{
$hash{USER}=$index->{USER};
$hash{PAGES}=$index->{PAGES};
}
push(#hash_ref_arr,{%hash});
}
But it gives me an error:
Global symbol "%index" requires explicit package name at ...
Maybe my logic isn't the best on this. Should I use arrays instead? It seems as though a hash is the best thing here, given the nature of my data. I just don't know how to go about slimming the array of hash refs down to just get a username and the total pages they printed (I know I seem redundant but I'm just trying to be clear). Thank you.
my %totals;
$totals{$_->{USER}} += $_->{PAGES} for #user_array_of_hash_refs;
And then, to get the data out:
print "$_ : $totals{$_}\n" for keys %totals;
You could sort by usage too:
print "$_ : $totals{$_}\n" for sort { $totals{$a} <=> $totals{$b} } keys %totals;
As mkb mentioned, the error is in the following line:
if(exists $index{$index->{USER}})
However, after reading your code, your logic is faulty. Simply correcting the syntax error will not provide your desired results.
I would recommend skipping the use of temporary hash within the loop. Just work with the a results hash directly.
For example:
#!/usr/bin/perl
use strict;
use warnings;
my #test_data = (
{ USER => "tom", PAGES => "5" },
{ USER => "mary", PAGES => "2" },
{ USER => "jane", PAGES => "3" },
{ USER => "tom", PAGES => "3" }
);
my $criteria = "USER";
my #sorted_users = sort { $a->{$criteria} cmp $b->{$criteria} } #test_data;
my %totals;
for my $index (#sorted_users) {
if (not exists $totals{$index->{USER}}) {
# initialize total for this user
$totals{$index->{USER}} = 0;
}
# add to user's running total
$totals{$index->{USER}} += $index->{PAGES}
}
print "$_: $totals{$_}\n" for keys %totals;
This produces the following output:
$ ./test.pl
jane: 3
tom: 8
mary: 2
The error comes from this line:
if(exists $index{$index->{USER}})
The $ sigil in Perl 5 with {} after the name means that you are getting a scalar value out of a hash. There is no hash declared by the name %index. I think that you probably just need to add a -> operator so the problem line becomes:
if(exists $index->{$index->{USER}})
but not having the data makes me unsure.
Also, good on you for using use strict or you would be instantiating the %index hash silently and wondering why your results didn't make any sense.
my %total;
for my $name_pages_pair (#sorted_users) {
$total{$name_pages_pair->{USER}} += $name_pages_pair->{PAGES};
}
for my $username (sort keys %total) {
printf "%20s %6u\n", $username, $total{$username};
}

Resources