Powershell Array not working in WinPE but OK on Windows 7 - arrays

Hi Firstly I have WinPE 4 and Powershell version 4 running other powershell scripts fine in my environment.
The issue is with displaying Array information.. On windows 7 running PS Version 4 this works great...
I have a Function rounding the size off which we dont need in this example.
$a = #()
$diskdrive = gwmi win32_diskdrive
foreach($drive in $diskdrive){
$InterFaceType = "$($Drive.InterfaceType)"
If(!($InterFaceType -eq "USB")){
$Size = "$($drive.size)"
$DriveModel = "`nDrive: $($drive.deviceid.substring(4)) Model: $($drive.model)"
$Size = Get-OptimalSize $Size
$Result = $DriveModel + " " + $Size
$a += $Result
}
}
$a = $a | Sort-Object
$Drive1 = $a[0]
$Drive2 = $a[1]
$Drive3 = $a[2]
$Drive4 = $a[3]
$Drive5 = $a[4]
$Drive6 = $a[5]
$Drive1 = $Drive1.trim()
$Drive2 = $Drive2.trim()
$Drive3 = $Drive3.trim()
$Drive4 = $Drive4.trim()
$Drive5 = $Drive5.trim()
$Drive6 = $Drive6.trim()
In Windows 7 $Drive1,2,3,4,5,6 will display the following info about the Drive detected.
Drive: PHYSICALDRIVE0 Model: ST3500418AS 465.76 GB
In WinPE $Drive1,2,3,4,5,6 Are Blank...
However $Result ( which i add to my array ) is not. As soon as i take that $Result and add to the $a array in WinPE its like it clears it?
I need to loop through the for each and add each pass to an array and this is how you do it, but as WinPE is acting differently I am looking at creating a brand new dynamic variable with each pass.
For eg.
Take first pass call $Result1, then $Result2 and $result3 etc.
Maybe if I use an actual Variable ( like the $Result which appears to display in WinPE) i can work round the odd behaviour of WinPE??
Any help appreciated.
Just to add the behaviour is reminiscent of the string not being trimmed as when compiling the code on Windows 7 it was doing the same until i added the trim to the $drive variables. However i have already trimmed the variables and in WinPE it still will not show when called. Do i have to trim further? just an idea
for eg.... i was doing this.
If($a[0]){
$Radio1.BackColor = [System.Drawing.Color]::’Transparent’
$System_Drawing_Size = New-Object System.Drawing.Size
$System_Drawing_Size.Width = 500
$System_Drawing_Size.Height = 15
$Radio1.Size = $System_Drawing_Size
$Radio1.TabIndex = 5
$Radio1.Text = $Drive1
$System_Drawing_Point = New-Object System.Drawing.Point
$System_Drawing_Point.X = 27
$System_Drawing_Point.Y = 260
$Radio1.Location = $System_Drawing_Point
$Radio1.DataBindings.DefaultDataSourceUpdateMode = 0
$Radio1.Name = "Radio1"
$form1.Controls.Add($Radio1)
}
Using $a and not an array this is what happens.
If i do this
$msg = [System.Windows.Forms.MessageBox]
$msg::Show($a[0])
I get nothing in winpe.
If i do this...
$msg = [System.Windows.Forms.MessageBox]
$msg::Show($a)
it outputs the information, however i cannot use just $a as that is only the last pass in the loop..
All this is fine in windows 7..
How can i create a New variable on each pass without an Array?
So $Result1, $Result2 etc then i can call the actual variable for the info not the array index? that might work?

Adding my suggestion as an Answer so the question can be resolved.
Try changing $a += $Results to [Array]$a = $a + $Results and see if that behaves better. It sounds like it isn't setting up $a as an array so it isn't iterating new records, it's just resetting it.

Related

How do multidimensional-arrays work in PowerShell?

I'm currently trying to create a program to train a foreign language.
For that i have two single dimensional array. In the first one i store all the foreign syllables or words and in the second one i'm storing the answers in my native language:
$QuestionArray = New-Object System.Collections.ArrayList
$QuestionArray = "Word1","Word2","Word3"
$AnswerArray = New-Object System.Collections.ArrayList
$AnswerArray = "Answer1","Answer2","Answer3"
Afterwards i check if the entered value is inside the answer-array. If it is i select the index and the index of the randomly selected word from the question-array. If both indexes match then the question has been answered correct otherwise its wrong.
$RandomQuestion = $QuestionArray | Get-Random
$Answer = $InputTextbox.Text
$IndexPositionQuestion = [array]::indexof($QuestionArray, $RandomQuestion)
$IndexPositionAnswer = [array]::indexof($AnswerArray, $Answer)
If($IndexPositionAnswer -eq $IndexPositionQuestion){
$RightTextbox.Text = $script:countercorrect++
}else{
$WrongTextbox.Text = $script:counterwrong++
}
The program works as intended but when i showed it to a colleague today he just told me that the comparing part of the program is coded ugly and is anything but best practice.
How could i go about it in any other way? I read a little bit about multidimensional-arrays but i just can't wrap my head around it. How would i benefit from multidimensional-arrays? How could i select the value i need from it to display, compare, check and so on?
Before digging into multidimensional arrays, why dont you try something like an array with hashtables?
$array = #(
#{
Question = 'blah?'
Answer = 'blub'
},
#{
Question = 'james'
Answer = 'Dean'
}
)
You can reference the values by
for($i = 0; $i -lt $array.Count; $i++){
$array[$i].Question
$array[$i].Answer
}
in youre example try something like
$RandomQuestionNr = 0..($QuestionArray.count -1) | Get-Random
$Answer = $InputTextbox.Text
if($array[$RandomQuestionNr].answer -eq $Answer){
$RightTextbox.Text = $script:countercorrect++
}else{
$WrongTextbox.Text = $script:counterwrong++
}

Using intermediate variable to work with array (reference type)

I am trying to use $a variable in this script for working with intermediate steps so that I don't have to use $array[$array.Count-1] repeatedly. Similarly for $prop as well . However, values are being overwritten by last value in loop.
$guests = Import-Csv -Path C:\Users\shant_000\Desktop\UploadGuest_test.csv
$output = gc '.\Sample Json.json' | ConvertFrom-Json
$array = New-Object System.Collections.ArrayList;
foreach ($g in $guests) {
$array.Add($output);
$a = $array[$array.Count-1];
$a.Username = $g.'EmailAddress';
$a.DisplayName = $g.'FirstName' + ' ' + $g.'LastName';
$a.Password = $g.'LastName' + '123';
$a.Email = $g.'EmailAddress';
foreach ($i in $a.ProfileProperties.Count) {
$j = $i - 1;
$prop = $a.ProfileProperties[$j];
if ($prop.PropertyName -eq "FirstName") {
$prop.PropertyValue = $g.'FirstName';
} elseif ($prop.PropertyName -eq "LastName") {
$prop.PropertyValue = $g.'LastName';
}
$a.ProfileProperties[$j] = $prop;
}
$array[$array.Count-1] = $a;
}
$array;
All array elements are referencing one actual variable: $output.
Create an entirely new object each time by repeating JSON-parsing:
$jsontext = gc '.\Sample Json.json'
..........
foreach ($g in $guests) {
$a = $jsontext | ConvertFrom-Json
# process $a
# ............
$array.Add($a) >$null
}
In case the JSON file is very big and you change only a few parts of it you can use a faster cloning technique on the changed parts (and their entire parent chain) via .PSObject.Copy():
foreach ($g in $guests) {
$a = $output.PSObject.Copy()
# ............
$a.ProfileProperties = $a.ProfileProperties.PSObject.Copy()
# ............
foreach ($i in $a.ProfileProperties.Count) {
# ............
$prop = $a.ProfileProperties[$j].PSObject.Copy();
# ............
}
$array.Add($a) >$null
}
As others have pointed out, appending $object appends a references to the same single object, so you keep changing the values for all elements in the list. Unfortunately the approach #wOxxOm suggested (which I thought would work at first too) doesn't work if your JSON datastructure has nested objects, because Copy() only clones the topmost object while the nested objects remain references to their original.
Demonstration:
PS C:\> $o = '{"foo":{"bar":42},"baz":23}' | ConvertFrom-Json
PS C:\> $o | Format-Custom *
class PSCustomObject
{
foo =
class PSCustomObject
{
bar = 42
}
baz = 23
}
PS C:\> $o1 = $o
PS C:\> $o2 = $o.PSObject.Copy()
If you change the nested property bar on both $o1 and $o2 it has on both objects the value that was last set to any of them:
PS C:\> $o1.foo.bar = 23
PS C:\> $o2.foo.bar = 24
PS C:\> $o1.foo.bar
24
PS C:\> $o2.foo.bar
24
Only if you change a property of the topmost object you'll get a difference between $o1 and $o2:
PS C:\> $o1.baz = 5
PS C:\> $o.baz
5
PS C:\> $o1.baz
5
PS C:\> $o2.baz
23
While you could do a deep copy it's not as simple and straightforward as one would like to think. Usually it takes less effort (and simpler code) to just create the object multiple times as #PetSerAl suggested in the comments to your question.
I'd also recommend to avoid appending to an array (or arraylist) in a loop. You can simply echo your objects inside the loop and collect the entire output as a list/array by assigning the loop to a variable:
$json = Get-Content '.\Sample Json.json' -Raw
$array = foreach ($g in $guests) {
$a = $json | ConvertFrom-Json # create new object
$a.Username = $g.'EmailAddress'
...
$a # echo object, so it can be collected in $array
}
Use Get-Content -Raw on PowerShell v3 and newer (or Get-Content | Out-String on earlier versions) to avoid issues with multiline JSON data in the JSON file.

Email Formatted Array

I got a script that creates two arrays (each has 1 column and variable number of lines). I want to format these two arrays and e-mail it to an Outlook account. Code and sample data below.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = #()
foreach ($Value in $Values2) {
$OutputLookUp2 += $Excel.WorksheetFunction.VLookup($Value,$range4,3,$false)
}
$Excel.Workbooks.Close()
$Excel.Quit()
$EmailFrom = "sample#sample.com"
$EmailTo = "sample#sample.com"
$EmailBody = "$Values2 $OutputLookup2"
$EmailSubject = "Test"
$Username = "sample"
$Password = "sample"
$Message = New-Object Net.Mail.MailMessage `
($EmailFrom, $EmailTo, $EmailSubject, $EmailBody)
$SMTPClient = New-Object Net.Mail.SmtpClient `
("smtp.outlook.com", portnumber) #Port can be changed
$SMTPClient.EnableSsl = $true
$SMTPClient.Credentials = New-Object System.Net.NetworkCredential `
($Username, $Password);
$SMTPClient.Send($Message)
Both $OutputLookUp2 and $Values2 are one column with variable number of lines.
Example:
$Outputlookup2 =
X1
X2
$Values2 =
Y1
Y2
I would like the output to the body of the e-mail to be:
X1 Y1
X2 Y2
And I would like to avoid HTML as it will be sent via text as well.
Assuming my interpretation is correct this seems simple enough. For every $Values2, which is just a line from a text file, find its similar value in the open spreadsheet. You are have the loop that you need. Problem is you are building the item lists independent of each other.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = #()
foreach ($Value in $Values2){
$OutputLookUp2 += "$Value $($Excel.WorksheetFunction.VLookup($Value,$range4,3,$false))"
}
Now $OutputLookUp2 should contain your expected output in array form.
If the array does not work you could also just declare it as a string and the add newlines as you are building it. You will notice the "`r`n" at the end of the string.
$Values2 = #(Get-Content *\IdealOutput.csv)
$OutputLookUp2 = ""
foreach ($Value in $Values2){
$OutputLookUp2 += "$Value $($Excel.WorksheetFunction.VLookup($Value,$range4,3,$false))`r`n"
}
In both example you can just flip the order of the $value and the lookup easy. If you need a header you can add that when you declare $OutputLookUp2.
There is always room for improvement
If you want to take this a little further in the direction that Ansgar Wiechers was eluding to...
$OutputLookUp2 = Get-Content *\IdealOutput.csv | ForEach-Object{
"$_ $($Excel.WorksheetFunction.VLookup($_,$range4,3,$false))"
} | Out-String

How to store outlook email body in array - Powershell?

the script below reads my outlook emails but how do I access the output. I'm new too Powershell and I'm still getting used to certain things. I just want to get the body of 10 unread outlook emails and store them in an Array called $Body.
$olFolderInbox = 6
$outlook = new-object -com outlook.application;
$ns = $outlook.GetNameSpace("MAPI");
$inbox = $ns.GetDefaultFolder($olFolderInbox)
#checks 10 newest messages
$inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$mBody = $_.body
#Splits the line before any previous replies are loaded
$mBodySplit = $mBody -split "From:"
#Assigns only the first message in the chain
$mBodyLeft = $mbodySplit[0]
#build a string using the –f operator
$q = "From: " + $_.SenderName + ("`n") + " Message: " + $mBodyLeft
#create the COM object and invoke the Speak() method
(New-Object -ComObject SAPI.SPVoice).Speak($q) | Out-Null
}
}
This may not be a factor here, since you're looping through only ten elements, but using += to add elements to an array is very slow.
Another approach would be to output each element within the loop, and assign the results of the loop to $body. Here's a simplified example, assuming that you want $_.body:
$body = $inbox.items | select -first 10 | foreach {
if($_.unread -eq $True) {
$_.body
}
}
This works because anything that is output during the loop will be assigned to $body. And it can be much faster than using +=. You can verify this for yourself. Compare the two methods of creating an array with 10,000 elements:
Measure-Command {
$arr = #()
1..10000 | % {
$arr += $_
}
}
On my system, this takes just over 14 seconds.
Measure-Command {
$arr = 1..10000 | % {
$_
}
}
On my system, this takes 0.97 seconds, which makes it over 14 times faster. Again, probably not a factor if you are just looping through 10 items, but something to keep in mind if you ever need to create larger arrays.
define $body = #(); before your loop
Then just use += to add the elements
Here's another way:
$body = $inbox.Items.Restrict('[Unread]=true') | Select-Object -First 10 -ExpandProperty Body

Powershell | Combining multiple Arrays and compare them to a table

I have encountered a new problem and I don't even know where exactly to start explaining. I will try my best, if something is unclear just ask me please.
I have an Excel workbook with informations (multiple rows) about DNS records - pretty similar to the powershell DNS syntax. e.g:
HostName RecordType TimeStamp TimeToLive RecordData
# A 0 00:05:00 127.0.0.1
I read them as arrays with the following little code - not very fast, but it works!:
#Read Excel
$row = [int]2
do {
if ($Sheet4.Cells.Item($Row,1).Text) {$ZoneName += $Sheet4.Cells.Item($Row,1).Text}
$HostName += $Sheet4.Cells.Item($Row,2).Text
$RecordType += $Sheet4.Cells.Item($Row,3).Text
$TimeStamp += $Sheet4.Cells.Item($Row,4).Text
$TimeToLive += $Sheet4.Cells.Item($Row,5).Text
$RecordData += $Sheet4.Cells.Item($Row,6).Text
$row = $row + [int] 1
} until (!$Sheet4.Cless.Item($row,2))
Now I have 6 arrays all stuck with information in different arrays, but all with the same amount of lines.
And now the tricky (atleast for me!) part:
I would like to stuff those 6 arrays into some special array I do not know, or in some sort of table I do not know how to create.
Why?
Because I want to compare those lines to this code ($Records to be specific):
$ZoneNames = (Get-DnsServerZone -ComputerName $DnsServer).zonename
$ZoneNames | foreach {$Records = (Get-DnsServerResourceRecord -ComputerName $DnsServer -ZoneName $_)}
$Records[0] would show me this (e.g.):
HostName RecordType Timestamp TimeToLive RecordData
-------- ---------- --------- ---------- ----------
# A 0 00:05:00 127.0.0.1
BUT: If I go deeper: $Records[0].RecordData:
IPv4Address PSComputerName
----------- --------------
127.0.0.1
So I would need to recreate this (above) sort of hierarchy to compare them (If I am right?).
I have tried it with a table like this (didn't work):
#Create Table object
$table = New-Object system.Data.DataTable “$ExcelRecords”
#Define Columns
$col2 = New-Object system.Data.DataColumn HostName,([string])
$col3 = New-Object system.Data.DataColumn RecordType,([string])
$col4 = New-Object system.Data.DataColumn TimeStamp,([string])
$col5 = New-Object system.Data.DataColumn TimeToLive,([string])
$col6 = New-Object system.Data.DataColumn RecordData,([string])
#Add the Columns
$table.columns.add($col2)
$table.columns.add($col3)
$table.columns.add($col4)
$table.columns.add($col5)
$table.columns.add($col6)
#Create a row
$r = $table.NewRow()
#Enter data in the row
$r.HostName = $HostName[$counter]
$r.RecordType = $RecordType[$counter]
$r.TimeStamp = $TimeStamp[$counter]
$r.TimeToLive = $TimeToLive[$counter]
$r.RecordData = $RecordData[$counter]
$RecordData
#Add the row to the table
$table.Rows.Add($r)
Tried comparing like this (didn't work):
if ($records[0] -like $table[0]) {write-host "works"}
This did work:
if ($records[0].hostname -like $table[0].hostname) {write-host "works"}
works
This did not (I guess this is the root of my problems):
if ($Records[0].RecordData -like $table[0].RecordData) {write-host "works"}
My main objective:
Check if there are Records on the DNS-Server, which aren't stated in the Excel sheet and delete them from the DNS-Server!
If you read through all the text, thanks for doing that! Appreciate every help.
Thanks in advance!
I'd start by creating an array of PS objects from your spreadsheet data.
#Read Excel
$row = [int]2
$DNS_Records =
do {
if ($Sheet4.Cells.Item($Row,1).Text) {
New-Object PSObject -Property #{
ZoneName = $Sheet4.Cells.Item($Row,1).Text
HostName = $Sheet4.Cells.Item($Row,2).Text
RecordType = $Sheet4.Cells.Item($Row,3).Text
TimeStamp = $Sheet4.Cells.Item($Row,4).Text
TimeToLive = $Sheet4.Cells.Item($Row,5).Text
RecordData = $Sheet4.Cells.Item($Row,6).Text
}
}
} until (!$Sheet4.Cless.Item($row,2))

Resources