combining objects and arrays with condition in powershell - powershell

My data($messages) looks like this
Source: sourceA
Message : {#{messagetext = abc;publishedtime = 10/5/2020}, #{messagetext = def;publishedtime = 10/12/2020}
I am trying to filter the message by displaying only messages that happened from last week until now then combining it with the source property. It is possible that a source may have multiple lines if several publishedtime happened last week.
$filterDate = (Get-Date).AddDays(-7)
$messages | select source, #{n='messagetext' ; e={???}} , #{n='publishedtime' ; e={???}}
I am lost on how to add the calculated property with the condition. I was also thinking if this could be done with a loop.
I also tried the following but having some errors
$Messages | Where-Object {([DateTime]($_.messages).publishedtime).ToUniversalTime() -ge $filterDate} but having some error

There is no need to use a calculated property. First filter the $messages collection by date. Like so,
$filterDate = (Get-Date).AddDays(-7)
$lastWeek = $messages | ? { $_.PublishedTime -ge $filterDate }
Now the $lastWeek collection contains only messages that were published within a week. Depending on the exact format of PublishedTime and your locale settings, it might need to be parsed as a date. If that's the case, please edit the question and show a few examples of actual data. Sanitize the messages if needed.

I think you should always compare dates to dates, and not compare a datetime object to a string, especially since in your question it is unclear what exact format the date is in.
Probably the format used is 'M/d/yyyy', but it could just aswell be 'd/M/yyyy' or even 'MM/d/yyyy'
$dateFormat = 'M/d/yyyy' # <-- this is unclear in the question, could also be 'd/M/yyyy' or 'MM/d/yyyy'
# you can set the Time part to all zero, as the dates in the $Messages objects also do not have that
$filterDate = (Get-Date).AddDays(-7).Date
$Messages | Where-Object {[DateTime]::ParseExact($_.Message.publishedtime, $dateFormat, $null) -ge $filterDate}

Related

Loop through multiple array in powershell

I have 2 arrays here one contains the servername and other contains the IP.
I need to loop through them and create a key value pair like below for each server
server1:ip1
server2:ip2
I have written below code, but the problem is if i debug the code using F11, it is working fine, but i don't it gives some error which is different every time.
so feeling like it is not that reliable piece to continue.
$NewDNSEntryName = $DNSEntryName.Split(",")
$DNSIPs = $DNSIP.Split(",")
if($DNSEntryName -match "," -or $DNSIP -match ",")
{
0..($NewDNSEntryName.Count - 1) | ForEach-Object {
$fullName=""
$fullName += #("$($NewDNSEntryName[$_]):$($DNSIPs[$_])")
This is the line where i am facing trouble
0..($NewDNSEntryName.Count - 1) | ForEach-Object
Please let me know why this code is behaving like this else any alternate idea is appreciated
Assuming each item in each list corresponds with each other exactly, you can use a for loop and loop through the array indexes.
$NewDNSEntryName = $DNSEntryName.Split(",")
$DNSIPs = $DNSIP.Split(",")
for ($i = 0; $i -lt $DNSIPs.count; $i++) {
"{0}:{1}" -f $NewDNSEntryName[$i],$DNSIPs[$i]
}
For the code above to work, $DNSEntryName and $DNSIP must be single strings with commas between names and IPs. If $DNSEntryName and $DNSIP are already lists or arrays, something else will need to be done.
In your attempt, technically, your logic should work given everything written above is true. However, $fullName is emptied at every single iteration, which may produce undesirable results.

powershell extracting data from strings or other suggestions

I have a script I am writing that essentially reads data from an excel document that is generated from another tool. It lists file ages in the format listed below. My issue is I would like to process each cell value and change the cell color based on that value. So anything older than 1 year gets changed to RED, 90+ days gets yellow\orange.
So after a bit of research, I elected to use an if statement to determine when it is greater than 0 years which seems to work fine, however when I reach the days portion I'm not sure how to extract JUST the digits portion to the left of d in each cell when you get to the y if its there just stop OR possibly just read the left digits only if the $_ contains d then I could further process if that value is -gt 90? I am unsure of how to extract variable length strings only if they are digits left of a character. I considered using a combination of the below method of finding a character and returning up to y or something else.
Find character position and update file name
Possible Age Formats:
13y170d
3y249d
8h7m
1y109d
1y109d
1y109d
5d22h
3y281d
3y184d
11y263d
7m25s
1h14m
[regex]$years = "\d{1,3}[0-9]y"
[regex]$days_90 = "\d{0,3}[0-9]d"
conditionally formatting/coloring row based on age (years)
if ( $( A$_ -match "$years") -eq $True ) {
$($test_home).$("Last Accessed") | ForEach-Object { $( $($_.Contains("y") -eq $True ) { New-ConditionalText -Text Red } }
conditionally formatting/coloring row based on age (90+ days)
if ( $( A$_ -match "$days_90") -eq $True ) { New-ConditionalText -Text Yellow }
What you are after is a positive lookahead and lookbehind. Effectivly it gets the text between two characters or sets. Really handy if you have a consistently formatted set of data to work with.
[regex]$days_90 = '(?<=y).*?(?=d)'
. Matches any characters without line breaks.
* Matches 0 or more of the preceding token.
? Makes the regex lazy and try to match as few as possible.

PowerShell filter specific form from string

I have a long string which contains letters, numbers, and other symbols.
I need to filter everything that matches the form number.number.number. For example 1.0.90 should pass the filter (it's a version number).
Afterwards, I need to convert the number after the last period (in the above example - 90) to a number which I can manipulate.
I didn't find any good explanation out there.
Use a regular expression to match the version number and capture the revision number for extraction (via the automatic variable $matches):
... | Where-Object {
$_ -match '\d+\.\d+\.(\d+)'
} | ForEach-Object {
$revision = [int]$matches[1]
}

extract filename and data from file

I have hourly log files from the past couple of months and i would like to export the data to MS Charts.
I've managed to get the data out, but having problems with getting the date on the x axis of the Chart.
The file name of the log file contains the date and i've tried using the creatation date or last write time. The closest i've got is to count the number of logs then divide by 24, but the dates it generates does not match up with the data.
Any ideas please?
Everything works as expected, but i cant get the X Axis to display the correct dates. in theory, it should calculate the 11 June as the start date, and the end date is yesterday's date.
the format of the file it reads from is
HealthCheck Wed 10 Sep 2014 - 05.00 AM.log
Ideally, I would like to get the date from the file name. I dont want to rely on calculations on when the file was written as this is prone to errors.
[void][Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")
[void][System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms.DataVisualization")
$Farm = "XAccess"
#gets files from yesterday
$Date = Get-Date #-Uformat %x
$Date = ($Date).adddays(-1)
$Date = $Date.ToString("M/d/yyyy")
$files = get-childitem "c:\$Farm*.log" | Where{$_.LastWriteTime -lt $date}
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {$_.Matches[0].Value}
#Calculates numbers of days
$datapoints = $ActiveSessions.count/24
#== Creates Chart ==#
# create chart object
$Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart
$Chart.Width = 1600
$Chart.Height = 800
# create a chartarea to draw on and add to chart
$ChartArea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea
$Chart.ChartAreas.Add($ChartArea)
[void]$Chart.Series.Add("Data")
$Chart.ChartAreas["ChartArea1"].AxisX.Interval = 24
$Chart.ChartAreas["ChartArea1"].AxisX.LabelStyle.Angle = -40
$Chart.ChartAreas["ChartArea1"].AxisY.Interval = 5
$Chart.ChartAreas["ChartArea1"].AxisY.title = "Active Sessions"
$chart.Series.Add('ChartArea1')
# add a data point for each server
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session)
$dp1.AxisLabel = (get-date).adddays(-"$datapoints").tostring("ddd dd MMM")
$datapoints = $datapoints - 0.0416666666666667
$Chart.Series["Data"].Points.Add($dp1)
}
# set the title to the date and time
$title = new-object System.Windows.Forms.DataVisualization.Charting.Title
$Chart.Titles.Add( $title )
$Chart.Titles[0].Font = "Arial,13pt"
$Chart.Titles[0].Text = "Year to Date sessions for $Farm"
# save the chart to a file
$Chart.SaveImage("C:\$Farm Farm.png","png")
Ok, so you want the date from the file. No problem, we can do that. Let's start with how best to do it... We have options (as usual) to choose from. Wait, I'm getting ahead of myself. Where do we want to define it? Well, simplest place I can think of is anyplace that you are looping through the files in question and getting other data, so we can keep everything properly associated. So, it looks like the lines that you assign $ActiveSessions is going to be our best bet.
Now, back to how to get the date. We could use the SubString method, but that just seems messy to me when we have a defined format for the text. Personally I'd rather do a regex match. So what I see in the file name is a three letter day abbreviation, then a 1-2 digit day, a three letter month, and a four digit year. After that there's a hyphen, and two digit hour, period, two digit minute, and the AM/PM designator.
So, as far as [datetime] formats go that's ddd d MMM yyyy - hh.mm tt. We'll get back to that. How to extract the date? Oh right, a regex. Here's how I'd get that:
([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value
That declares the pattern as a regex object, and then uses it's Matches() method, and get the value of the match. So we have the date, now to actually make it usable. Here's where we get back to that DateTime format. We can use the [DateTime]::ParseExact() method to get the date time, even with the strange formatting. Now we are going to be getting it in the ForEach loop, and are extracting the date from the FileName property of the object that Select-String is feeding the loop. Here's what it will look like:
[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
Ok, that actually gives us a nice usable datetime object. So that ForEach loop is already spitting back the active sessions, and $ActiveSessions is an array of strings. Let's change that a little and make it an array of objects, and each object will have two properties now, Sessions and Date. So the inside of the ForEach loop has to make an object with those properties. Easiest way (with PowerShell v3 or higher, I'll show you the hard way if you are running an old version of PowerShell and need me to) is:
[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
That makes the whole $ActiveSessions = line look like:
$ActiveSessions = Select-String -path $files '(?<=^"*Total Active Sessions: )\d+(?=)'|
ForEach-Object {[PSCustomObject][Ordered]#{
'Sessions'=$_.Matches[0].Value
'Date'=[datetime]::ParseExact(([regex]"\w{3} \d{1,2} \w{3} \d{4} - \d{2}\.\d{2} (?:AM|PM)").matches($_.Filename).value,"ddd d MMM yyyy - hh.mm tt",$null)
}
}
Only thing that leaves is to change where that variable is referenced:
foreach ($session in $ActiveSessions)
{
$dp1 = new-object System.Windows.Forms.DataVisualization.Charting.DataPoint(0, $session.session)
$dp1.AxisLabel = $Session.Date.tostring("ddd dd MMM")
$Chart.Series["Data"].Points.Add($dp1)
}
That should do it for you.
I tried using regex but could not get pass the error i mentioned.
so after a bit more research, i decided exporting the creation date of the file and the data i needed into a CSV file, then creating the chart from that. So this is the code i used to get the data in to csv:
$files = get-childitem "c:\$farm*.log" | sort CreationTime
Foreach ($file in $files)
{
$FileCreation = $file.CreationTime.Date.ToString('ddd dd MMM yyyy')
$ActiveSessions = Select-String -path $file '(?<=^"*Total Active Sessions: )\d+(?=)' | ForEach-Object {$_.Matches[0].Value}
Add-Content d:\licInUse.csv "$FileCreation,$ActiveSessions"
}
# processing the Data
$Processes = Import-Csv -path d:\licInUse.csv -Delimiter ',' -Header "Date","Count"
$DateNames = #(foreach($Date in $Processes){$Date.Date})
$SessionCount = #(foreach($Date in $Processes){$Date.Count})
then used the following to plot the data
$Chart.Series["Data"].Points.DataBindXY($DateNames, $SessionCount)

Returning a Row Count for a DataRow in PowerShell

My script is populating a datarow from a stored procedure in SQL Server. I then reference specific columns in this datarow throughout the script. What I'm trying to do is add functionality that takes action X if the row count = 0, action Y if the row count = 1, and action Z if the row count > 1.
-- PowerShell script snippet
# $MyResult is populated earlier;
# GetType() returns Name=DataRow, BaseType=System.Object
# this works
ForEach ($MyRow In $MyResult) {
$MyFile = Get-Content $MyRow.FileName
# do other cool stuff
}
# this is what I'm trying to do, but doesn't work
If ($MyResult.Count -eq 0) {
# do something
}
ElseIf ($MyResult.Count -eq 1) {
# do something else
}
Else {
# do this instead
}
I can get $MyResult.Count to work if I'm using an array, but then I can't reference $MyRow.FileName directly.
This is probably pretty simple, but I'm new to PowerShell and object-oriented languages. I've tried searching this site, The Scripting Guy's blog, and Google, but I haven't been able to find anything that shows me how to do this.
Any help is much appreciated.
It has everything to do with how you populate $MyResult. If you query the database like
$MyResult = #( << code that returns results from database >> )
that is, enclosing the code that returns your dataset/datatable from the database within #( ... ), then number of rows returned will be easily checked using $MyResult.count.
Your original code should work as-is if you populate $MyResult this way.
I know this thread is old, but if someone else finds it on Google, this should work also on PS V5:
Replace $MyResult.Count with: ($MyResult | Measure-Object | select -ExpandProperty Count)
For Example:
If (($MyResult | Measure-Object | select -ExpandProperty Count) -eq 0)
I don't have experience with PS and SQL, but I'll try to provide an answer for you. If you're object $myresult is a datarow-object, it means you only got the one row. If the results are empty, then $myresult will usually be null.
If you get one or more rows, you can put them in an array and count it. However, if your $myresult are null, and you put it in an array it will still count as one, so we need to watch out for that. Try this:
If ($MyResult -eq $null) {
# do something if no rows
}
Else If (#($MyResult).Count -eq 1) {
# do something else if there are 1 rows.
# The cast to array was only in the if-test,
# so you can reach the object with $myresult.
}
Else {
# do this if there are multiple rows.
}
Looks like this question gets a lot of views, so I wanted to post how I handled this. :)
Basically, the fix for me was to change the method I was using to execute a query on SQL Server. I switched to Chad Miller's Invoke-SqlCmd2 script: TechNet: Invoke-SqlCmd2, i.e.
# ---------------
# this code works
# ---------------
# Register the function
. .\Invoke-Sqlcmd2.ps1
# make SQL Server call & store results to an array, $MyResults
[array]$MyResults = Invoke-Sqlcmd2 -Serve
rInstance "(local)" -Query "SELECT TOP 1 * FROM sys.databases;"
If ($MyResult -eq $null) {
# do something
}
ElseIf ($MyResult.Count -eq 1) {
# do something else
}
Else {
# do this instead
}