Powershell passing array to other script, turns to system hastable? Fix? - arrays

In Powershell I am attempting to pass an array as an argument to a second script, however this, according to the debugger, is managed by the second script as a system.collection.hashtable object. How can I fix this?
calling script code
$images = #{
image1 = "$dir\welcomeMessageAtaP1_files\image001.jpg"
}
..\sendmail.ps1 –subject $subject1 –body $fixed3 -recipient $personalemail -images $images
sendmail.ps1 code
param (
[string]$subject = "** EMPTY **",
[string]$body = "** EMPTY **",
[string]$recipient = "email#somedomain",
[string[]] $images=#()
)
$params = #{
InlineAttachments = $images
Body = $body
BodyAsHtml = $true
Subject = $subject
From = "emailsender#somedomain"
To = $recipient
SmtpServer = 'smtp.gmail.com'
Port = 587
UseSsl = $true
}
Send-MailMessage #params -Credential $cred
Throws the error "Send-MailMessage : Cannot process argument transformation on parameter 'InlineAttachments'. Cannot convert the "System.String[]" value of type "System.String[]" to type "System.Collections.Hashtable".
Tried also to change param to [string] $images=#()

You indeed pass a Hashtable. #{} does create a Hashtable and you have a Key Image1 and a value "$dir\welcomeMessageAtaP1_files\image001.jpg"
$images = #{
image1 = "$dir\welcomeMessageAtaP1_files\image001.jpg"
}
instead pass an array #():
$images = #(
"$dir\welcomeMessageAtaP1_files\image001.jpg"
)

Related

PowerShell problem with array and grouping objects

I am having problems to group an array of objects by Environment. The following script show three environments with servers in each one. Now, I am trying to loop those objects by groups but getting errors with the Environment names. The following is my sample logic:
$array = #(("DEVELOPMENT", ("SRV1")), ("QA", ("SRV2", "SRV3")), ("PRODUCTION", ("SRV4", "SRV5")))
ForEach ($system in $array) {
$envName = $array[0]
ForEach ($hostname in $system[1]) {
Write-Host ("Result for " + $hostname + " in " + $envName)
}
}
The variable name $envName always returns the same wrong result.
[0]:"DEVELOPMENT"
[1]:"SRV1"
How can I group and loop $array[0] and $system[1] in the following way?
DEVELOPMENT = SRV1
QA = SRV2, SRV3
PRODUCTION = SRV4, SRV5
Another option would be to create an object array instead of an embedded string array.
class Server
{
[string]$Environment
[string]$ServerName
}
$Development = #("SRV1")
$Qa = #("SRV2", "SRV3")
$Production = #("SRV4", "SRV5")
$Array = #()
foreach ($System in $Development)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "Development"
$Array += $Server
}
foreach ($System in $Qa)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "QA"
$Array += $Server
}
foreach ($System in $Production)
{
$Server = [Server]::new()
$Server.ServerName = $System
$Server.Environment = "Production"
$Array += $Server
}
foreach ($System in $Array)
{
Write-Host "Result for $($System.ServerName) in $($System.Environment)"
}
The solution in the comments
"Instead of $envName = $array[0] you should write $envName = $system[0]"

Powershell: PSCustomObject array as parameter in function gets changed unexpectedly

In the simplified PS code below, I don't understand why the $MyPeople array gets changed after calling the changeData function. This array variable should just be made a copy of, and I expect the function to return another array variable into $UpdatedPeople and not touch $MyPeople:
function changeData {
Param ([PSCustomObject[]]$people)
$changed_people = $people
$changed_people[0].Name = "NEW NAME"
return $changed_people
}
# Original data:
$Person1 = [PSCustomObject]#{
Name = "First Person"
ID = 1
}
$Person2 = [PSCustomObject]#{
Name = "Second Person"
ID = 2
}
$MyPeople = $Person1,$Person2
"`$MyPeople[0] =`t`t" + $MyPeople[0]
"`n# Updating data..."
$UpdatedPeople = changeData($MyPeople)
"`$UpdatedPeople[0] =`t" + $UpdatedPeople[0]
"`$MyPeople[0] =`t`t" + $MyPeople[0]
Console output:
$MyPeople[0] = #{Name=First Person; ID=1}
# Updating data...
$UpdatedPeople[0] = #{Name=NEW NAME; ID=1}
$MyPeople[0] = #{Name=NEW NAME; ID=1}
Thanks!
PSObject2 = PSObject1 is not a copy but a reference. You need to clone or copy the original object using a method designed for that purpose.
function changeData {
Param ([PSCustomObject[]]$people)
$changed_people = $people | Foreach-Object {$_.PSObject.Copy()}
$changed_people[0].Name = "NEW NAME"
return $changed_people
}
The technique above is simplistic and should work here. However, it is not a deep clone. So if your psobject properties contain other psobjects, you will need to look into doing a deep clone.
We can clone the PSCustomObject. We will create a new PSObject and enumerate through the psobject given as parameter and add them one by one to the shallow copy.
function changeData {
Param ([PSCustomObject[]]$people)
$changed_people = New-Object PSobject -Property #{}
$people.psobject.properties | ForEach {
$changed_people | Add-Member -MemberType $_.MemberType -Name $_.Name -Value $_.Value
}
$changed_people[0].Name = 'NEW NAME'
return $changed_people
}
Or use another method by #AdminOfThings

Return an array from a function in PowerShell 3.0

I am trying to have PowerShell 3.0 return an array from a function. Unfortunately this is not very well documented as I have spent the past two days scouring Google for examples on this. I am this close to rewriting the entire script in C# and calling it a day.
The script checks a set of URLS which are contained in a variable. The function in question gets the list of URL's from an array and loops through the array adding the HTTP status code to a new array. The function does all of the above, however it does not return the array. Here is the function in question:
function URLCheck ($URLStatusCode)
{
foreach($uri in $URLStatusCode )
{
$result = #()
$time = try
{
$request = $null
## Check response time of requested URI.
$result1 = Measure-Command { $request = Invoke-WebRequest -Uri $uri}
$result1.TotalMilliseconds
}
catch
{
<# If request generates exception such as 401, 404 302 etc,
pull status code and add to array that will be emailed to users #>
$request = $_.exception.response
$time = -1
}
$result += [PSCustomObject] #{
Time = Get-Date;
Uri = $uri;
StatusCode = [int] $request.StatusCode;
StatusDescription = $request.StatusDescription;
ResponseLength = $request.RawContentLength;
TimeTaken = $time;
}
}
return $result
}
I call the like this:
URLCheck $LinuxNonProdURLList
$result
I also printed the contents of $result after the execution and I notice that it is empty. However if I were to put the return statement in the foreach loop, it does send the information to the console.
Any help would be greatly appreciated.
After a little more troubleshooting I figured out that the array $result was only local to the function. I declared the array outside of the foreach loop and that fixed the error. Here is the updated code:
function URLCheck ($URLStatusCode)
{
$result = #()
foreach($uri in $URLStatusCode )
{
$time = try
{
$request = $null
## Check response time of requested URI.
$result1 = Measure-Command { $request = Invoke-WebRequest -Uri $uri}
$result1.TotalMilliseconds
}
catch
{
<# If request generates exception such as 401, 404 302 etc,
pull status code and add to array that will be emailed to users #>
$request = $_.exception.response
$time = -1
}
$result += [PSCustomObject] #{
Time = Get-Date;
Uri = $uri;
StatusCode = [int] $request.StatusCode;
StatusDescription = $request.StatusDescription;
ResponseLength = $request.RawContentLength;
TimeTaken = $time;
}
}
return $result
}

Create an object from an array

I have a string of data which I turn into an array thus:
$cfg_data = #(
"AppName1,data1",
"AppName2,data2"
)
I then run a foreach query to work on each line at a time:
FOREACH($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
#Do stuff here
}
But I want to convert this from a string to an Object, so I can add/remove additional items,
and then run some more foreach statements, updating the different bits as I go.
I have got as far as:
FOREACH($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere = #(
#{
appItem = "$AppN";
appData = "$AppD";
})
}
But when I check $objHere it just has the last entry in it (AppName2, datat2)
I tried adding:
$b=0
and
$objHere[$b]
but then I get
Array assignment failed because index '1' was out of range.
What is the correct way to do this?
By declaring $objHere in the loop, you overwrite the value on each iteration. You need to initialise an empty array outside the loop and append to it from within the loop:
$objHere = #()
foreach($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere +=
#{
appItem = "$AppN";
appData = "$AppD";
}
}
In addition, you're not actually creating an object, you're creating a hashtable. If you wanted to create an object instead, you could do this:
$objHere = #()
foreach($a in $cfg_data)
{
$dataSplit = $a -split"(,)"
$AppN = $dataSplit[0]
$AppD = $dataSplit[2]
$objHere += New-Object PSObject -property #{appItem = "$AppN";appData = "$AppD";}
}
Giving:
appItem appData
------- -------
AppName1 data1
AppName2 data2

Error when modifying object that is not being enumerated

I have the following script:
$serverList = #{
"Server1Name" = #{ "WindowsService1" = "Status"; "WindowsService2" = "Status" };
"Server2Name" = #{ "WindowsService1" = "Status"; "WindowsService2" = "Status" };
"Server3Name" = #{ "WindowsService1" = "Status" };
"Server4Name" = #{ "WindowsService1" = "Status" };
"Server5Name" = #{ "WindowsService1" = "Status" };
"Server6Name" = #{ "WindowsService1" = "Status" }
}
$copy = $serverList.Clone()
foreach ($server in $copy.Keys) {
foreach ($service in $copy[$server].Keys) {
$serviceInfo = Get-Service -ComputerName $server -Name $service
$serverList[$server][$service] = $serviceInfo.Status
}
}
I made sure that I am not modifying the hashtable that is being enumerated, but yet I still get this error when I run the script:
Collection was modified; enumeration operation may not execute.At line:14 char:14
+ foreach ($service in $copy[$server].Keys) {
+ ~~~~~~~~
+ CategoryInfo : OperationStopped: (:) [], InvalidOperationException
+ FullyQualifiedErrorId : System.InvalidOperationException
I read up on this here: http://blog.matticus.net/2013/11/powershell-clearing-values-within.html. If I copy the code form there, it executes without error for me.
Could my problem have something to do with nested foreach loops? Is there a mistake in my code? Can anyone shed any light on this?
Powershell does not like that you are modifying the collection which you are iterating over.
In the beginning you made a clone called $copy to avoid this problem. The clone() is a "shallow copy", thus the objects being refered to for each key are the same in your copy.
On this line:
$serverList[$server][$service] = $serviceInfo.Status
You modify the inner collection - which you are currently iterating over.
In fact, the outter collection is never modified, only referred to, so the outter clone() call is unneccessary. Instead, you should clone the inner collection.
Something like this (untested):
$serverList = #{
"Server1Name" = #{ "WindowsService1" = "Status"; "WindowsService2" = "Status" };
"Server2Name" = #{ "WindowsService1" = "Status"; "WindowsService2" = "Status" };
"Server3Name" = #{ "WindowsService1" = "Status" };
"Server4Name" = #{ "WindowsService1" = "Status" };
"Server5Name" = #{ "WindowsService1" = "Status" };
"Server6Name" = #{ "WindowsService1" = "Status" }
}
foreach ($server in $serverList.Keys) {
$copy = $serverList[$server].clone();
foreach ($service in $copy.Keys) {
$serviceInfo = Get-Service -ComputerName $server -Name $service
$serverList[$server][$service] = $serviceInfo.Status
}
}
I was surprised that the .Clone() method just creates a new reference to the same object, it does not create a new object with the same properties. I couldn't find an easy way to actually copy an entire hashtable, rather than cloning it. So I wrote a function to do this:
Function Copy-HashTable($HashTable) {
$newHash = #{}
$HashTable.GetEnumerator() | ForEach-Object {
if ($_.Value -is "Hashtable") {
$newHash[$_.Key] = Copy-HashTable $_.Value
} else {
$newHash[$_.Key] = $_.Value
}
}
$newHash
}
Applying this to your code, you would just need to replace the line
$copy = $serverList.Clone()
with
$copy = Copy-HashTable $ServerList

Resources