Array exporting to csv from powershell stack issue - arrays

I am creating a reporting script in powercli for our vSphere.
I am trying to make an array that i can export to csv.
The problem i am having is i need to have multiple rows to input into the array, not just the same one over and over.
When i run in current format all that happens is the outermost row reports and none of the internal ones
Here is a mockup of what i want the script to do.
$report = #()
foreach($D in $data){
$row = "" | select Datacenter, datacenterInfo
$row.datacenter = "kam1"
$row.datacenterInfo = "12"
$report += $row
foreach($F in $D){
$row2 = "" | select folder, folderInfo
$row2.folder = "test"
$row2.folderInfo = "34"
$report += $row2
foreach($VM in $F){
$row3 = "" | select VM, VMInfo
$row3.VM = "test"
$row3.VMInfo = "56"
$report += $row3
}
}
}
$report | export-csv -path c:\temp\testy.csv -NoTypeInformation
Any Help Appreciated
data would hopefully come out as a csv in excel while looking like so
datacenter / datacenterinfo
test / 12
folder / folderinfo
test / 34
VM / VMInfo
test / 56
The real script would be detailing hundreds of VM's and folder though.

I know this problem and have no real Solution for it.
But as workaround you could use
"$row3.VM \ $row3.VMInfo" | Out-File -append -path "c:\temp\testy.csv"
in your inner for each, and write the data manually as csv.

Related

Powershell Looping through eventlog

I am trying to gather data from eventlogs of logons, disconnect, logoff etc... this data will be stored in a csv format.
This is the script i am working which got from Microsoft Technet and i have modified to meet my requirement. Script is working as it should be but there is looping going on which i can't figure out how it should be stopped.
$ServersToQuery = Get-Content "C:\Users\metho.HOME\Desktop\computernames.txt"
$cred = "home\Administrator"
$StartTime = "September 19, 2018"
#$Yesterday = (Get-Date) - (New-TimeSpan -Days 1)
foreach ($Server in $ServersToQuery) {
$LogFilter = #{
LogName = 'Microsoft-Windows-TerminalServices-LocalSessionManager/Operational'
ID = 21, 23, 24, 25
StartTime = (Get-Date).AddDays(-1)
}
$AllEntries = Get-WinEvent -FilterHashtable $LogFilter -ComputerName $Server -Credential $cred
$AllEntries | Foreach {
$entry = [xml]$_.ToXml()
$Output += New-Object PSObject -Property #{
TimeCreated = $_.TimeCreated
User = $entry.Event.UserData.EventXML.User
IPAddress = $entry.Event.UserData.EventXML.Address
EventID = $entry.Event.System.EventID
ServerName = $Server
}
}
}
$FilteredOutput += $Output | Select TimeCreated, User, ServerName, IPAddress, #{Name='Action';Expression={
if ($_.EventID -eq '21'){"logon"}
if ($_.EventID -eq '22'){"Shell start"}
if ($_.EventID -eq '23'){"logoff"}
if ($_.EventID -eq '24'){"disconnected"}
if ($_.EventID -eq '25'){"reconnection"}
}
}
$Date = (Get-Date -Format s) -replace ":", "-"
$FilePath = "$env:USERPROFILE\Desktop\$Date`_RDP_Report.csv"
$FilteredOutput | Sort TimeCreated | Export-Csv $FilePath -NoTypeInformation
Write-host "Writing File: $FilePath" -ForegroundColor Cyan
Write-host "Done!" -ForegroundColor Cyan
#End
First time when i run the script, it runs fine and i get the csv output as it should be. When i run the script again than a new CSV is created (as it should be) but the same event log enteries are created twice and run it again than three enteries are created for the same event. This is very strange as a new csv is created each time and i dont not have -append switch for export-csv configured.
$FilteredOutput = #()
$Output = #()
I did try adding these two lines in above script as i read somewhere that it is needed if i am mixing multiple variables into a array (i do not understand this so applogies if i get this wrong).
Can someone please help me this, more importantly, I need to understand this as it is good to know for my future projects.
Thanks agian.
mEtho
It sounds like the$Output and $FilteredOutput variables aren't getting cleared when you run the script subsequent times (nothing in the current script looks to do that), so the results are just getting appended to these variables each time.
As you've already said, you could add these to the top of your script:
$FilteredOutput = #()
$Output = #()
This will initialise them as empty arrays at the beginning, which will ensure they start empty as well as make it possible for them to be appended to (which happens at the script via +=). Without doing this on the first run the script likely failed, so I assume you must have done this in your current session at some point for it to be working at all.

Merge two vlc playlists, with an order of 2:1 or 3:1

Wasn't sure how to explain what I want with a title.
Basically i have two folders of videos. All with random titles. What i would like to do, is create a windows batch file, that basically puts all filenames into one vlc, but in a set order. For example
Folder A, folder B: -
A
A
B
A
A
B
or
A
A
A
B
A
A
A
B
I found this online when googling "dir /a /b /-p /o:gen >A.vlc" this obviously generates the vlc list from a directory, and i can do that with the other directory, but i then need to combine them with the layout as above.
Or is there a better way to do it?
It has to be a windows batch file (or at least something that can run with windows scheduler)
Many thanks in advance.
Surely there must be some better way to do this. Here is a simplistic PowerShell script to merge the two directories based on a ratio; Acount:Bcount. I want to think there is an easier way.
Put this code in a file with a .ps1 extension. Perhaps mix.ps1. If you are not familiar with how to run PowerShell scripts, search the web. It has been explained a gabillion and a half times.
$Alist = Get-ChildItem -File -Path "A" | ForEach-Object { $_.FullName }
$Blist = Get-ChildItem -File -Path "B" | ForEach-Object { $_.FullName }
$Acount = 2
$Bcount = 1
$Astart = 0
$Bstart = 0
$Total = $Alist.Length + $Blist.Length
$i = 0
while ($i -lt $Total) {
if ($Astart -lt $Alist.Length) {
$Alist[$Astart..$($Astart+$Acount-1)]
$i += $Alist[$Astart..$($Astart+$Acount-1)].Length
$Astart += $Acount
}
if ($Bstart -lt $Blist.Length) {
$Blist[$Bstart..$($Bstart+$Bcount-1)]
$i += $Blist[$Bstart..$($Bstart+$Bcount-1)].Length
$Bstart += $Bcount
}
}

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

How can I import a .csv into multiple arrays in a simpler way?

I am new to powershell and am writing my first somewhat complicated script. I would like to import a .csv file and create multiple text arrays with it. I think that I have found a way that will work but it will be time consuming to generate all of the lines that I need. I assume I can do it more simply using foreach-object but I can't seem to get the syntax right.
See my current code...
$vmimport = Import-Csv "gss_prod.csv"
$gssall = $vmimport | ForEach-Object {$_.vmName}`
$gssweb = $vmimport | Where-Object {$_.tier -eq web} | ForEach-Object {$_.vmName}
$gssapp = $vmimport | Where-Object {$_.tier -eq app} | ForEach-Object {$_.vmName}
$gsssql = $vmimport | Where-Object {$_.tier -eq sql} | ForEach-Object {$_.vmName}
The goal is to make 1 group with all entries containing only the vmName value, and then 3 separate groups containing only the vmName value but using the tier value to sort them.
Can anyone help me with an easier way to do this?
Thanks!
For the last three you can group the object by the Tier property and have the result as a hasthable. Then you can reference the Tier name to get its VMs.
#group objects by tier
$gs = $vmimport | Group-Object tier -AsHashTable
# get web VMs
$gs['web']
# get sql VMs
$gs['app']
You may want to use a dictionary for storing the data:
$vmimport = Import-Csv "gss_prod.csv"
$gssall = $vmimport | % { $_.vmName }
$categories = "web", "app", "sql", ...
$gss = #{}
foreach ($cat in $categories) {
$gss[$cat] = $vmimport | ? { $_.tier -eq $cat } | % { $_.vmName }
}
I like the Shay Levy way, but the values of hash tables remain hash tables. Here is an other more efficient approach where values are jagged arrays, and categories are made automatically (contrary to Ansgar Wiechers solution):
# define hashtable
$gs = #{};
# fill it
$vmimport | foreach {$gs[$_.tier]+=, $_.vmName};
# get web VMs
$gs['web'] # the result is an array of 'web' vmNames.

Format array and include in e-mail using powershell

I am trying to build a powershell script which collects system up-time information from multiple system and then e-mails it. I want to format the e-mail so I can structure it in a sensible way and later add more information about the systems.
Today I have this script below which collect the uptime information about a list of systems and then passes these information in a array to the SendMail function which includes the array in an e-mail. However the current output is like this.
Uptime list: Restarted 02/19/2013 04:04:52 MyServer01 Computer Uptime: 23 days and 9 hours Restarted 02/15/2013 04:17:40 MyServer02 Computer Uptime: 27 days and 9 hours
I would like to have the output or rather the e-mail text more like:
System | Restarted date/time | Uptime
MyServer01 02/17/2013 04:04:52 25 days and 9 hours
MyServer02 02/17/2013 04:04:52 25 days and 9 hours
How can I format the text in the e-mail or do I have to do that before I pass the array to the e-mail function.
The script itself:
`
function SendMail([string]$arg1){
#SMTP server name
$smtpServer = "mysmtp"
#Creating a Mail object
$msg = new-object Net.Mail.MailMessage
#Creating SMTP server object
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
#Email structure
$msg.From = "my#overtherainbow.ddd"
$msg.ReplyTo = "noreply#overtherainbow.ddd"
$msg.To.Add("my#overtherainbow.ddd")
$msg.subject = "System uptime"
$msg.body = ("List og servers and uptime:" + $arg1 )
#Sending email
$smtp.Send($msg)}
function CheckUptime($listofservers){
$k =#()
foreach($s in $listofservers){
$Booted = Get-WmiObject -Class Win32_OperatingSystem -Computer $s
$Calc = [DateTime]::Now - $Booted.ConvertToDateTime($Booted.LastBootUpTime)
$k += " Restarted " +
$Booted.ConvertToDateTime($Booted.LastBootUpTime) +
" " + $s + " " +
" Computer Uptime: " +
$Calc.days + " " +
"days and", $Calc.hours + " hours"
}
return $k
}
#Defining list og servers
$listofservers = "MyServer01", "MyServer02"
#Calling function
$message = CheckUptime($serverlist)
SendMail($message)
`
Use Format-Table to format the data as desired, and pipe that into Out-String to convert into a single string containing the formatted data.
Then append that to your email content.
This relies on the data gathering replacing building a single string with building a custom object with separate fields for each different column.
Eg (incomplete, but should give you the idea)
$serverData = $serverList | Foreach-Object { GetServerData $_ } |
Format-Table Name,
#(l='Boot Time'; e={$_.BootTime.ToLocalTime()}},
#(l='Uptime'; e={FormatUptime([DateTime]::UtcNow - $_.BootTime}} |
Out-String
$emailBody += $serverData
where the referenced functions are already defined, along the lines of:
function GetServerData {
params([string]$name)
$os = Get-WmiObject -Class Win32_OperatingSystem -Computer $name
$bootTimeLocal = $os.ConvertToDateTime($os.LastBootUpTime)
new-object PSObject -property #{
Name = $name;
# Keep everything in UTC to avoid confusion in calcs,
# TODO FIX: Use the remote machine's timezone for this....
BootTime = $bootTimeLocal.ToUniversalTime()
}
}
and
function FormatUptime {
params([TimeSpan]$time)
...
}
Summary:
Use Format-* cmdlets to format, and if not immediately passing to output (and the default Out-Default appended to the end of every pipeline without an Out-*) then convert to a string with Out-String.
Keep data types in their native type as long as possible. Do not convert to human readable forms until you have to. Thus information is not lost.
Make use of custom objects and properties (especially NoteProperties) to create your own object "types" to keep related information together.
First, it shows little effort when your sample is not even a working sample. Variable typos etc.
As a solution, why don't you create custom objects containing the data for each server and output it as an array?. Something like:
PS > function CheckUptime($listofservers){
$res = #()
foreach($s in $listofservers){
$boot = Get-WmiObject win32_operatingsystem -ComputerName $s | % { $_.ConvertToDateTime($_.LastBootUpTime) }
$uptime = [DateTime]::Now - $boot
$res += New-Object psobject -Property #{
Server = (Get-Culture).TextInfo.ToTitleCase($s)
BootTime = $boot
Uptime = "{0} days and {1} hours" -f $uptime.Days, $uptime.Hours
}
}
#Output
$res
}
$myservers = "localhost", "localhost"
$str = "List of servers and uptime:`r`n"
$str += CheckUptime $myservers | Out-String
PS > $str
List of servers and uptime:
Server Uptime BootTime
------ ------ --------
Localhost 8 days and 6 hours 06.03.2013 09:33:37
Localhost 8 days and 6 hours 06.03.2013 09:33:37

Resources