I've got a Powershell script that uses Get-EventLog to search for events 6005, 6006, and 6008 on remote servers; after a little manipulation, it returns the results in an array.
$eventData += Get-EventLog -computerName $server_name -LogName system `
-After $prevMonthBegin -Before $prevMonthEnd |
Where-Object `
{ $_.eventid -eq 6005 -OR $_.eventID -eq 6006 -OR $_.eventID -eq 6008 } |
Select timegenerated, eventid, message, index, recordid | sort-object timegenerated
I loop through the results, and assign either down or up to the $eventData.message field and sort it again by timegenerated This returns an array like so (apologies for the formatting):
$eventData | Sort-Object timegenerated | format-table
TimeGenerated EventID Message Index
------------- ------- ------- ----- --------
8/3/2014 5:30:02 AM 6006 down 0
8/3/2014 5:30:47 AM 6005 up 0
8/24/2014 5:31:00 AM 6005 up 0
8/31/2014 2:34:59 AM 6008 down 1
8/31/2014 5:30:04 AM 6006 down 0
8/31/2014 5:30:59 AM 6005 up 0
8/31/2014 5:36:26 AM 6005 up 0
How can I create a new array from these results and arrange the TimeGenerated into up/down pairs? I would prefer to disregard the first event of the array if it is anup (so there's always down then up in the pairs), but I can work around this. It's more important to never have two consecutive down or up events, as in the example output above.
I was thinking of maybe iterating through the events and toggling a temp variable between 1 and 0 (or up and down) but that seems clunky and I just can't make anything work. It shouldn't be this hard, but that's why I'm asking for help. Let me know if providing more code would be useful; there's a quite a bit of it and I didn't want to just dump pages of it.
Here's an example of the format for the array that I would like to have. The method that I'm using pairs these events as 12121212 without looking at what types of events they are. So in the above example, the last event pair would calculate downtime where it did not exist because there were two up (6005) events.
Downtime Uptime CrashEvent?
8/3/2014 5:30:02 AM 8/3/2014 5:30:47 AM 0
8/24/2014 5:31:00 AM 8/31/2014 2:34:59 AM 0
8/31/2014 2:34:59 AM 8/31/2014 5:30:04 AM 1
8/31/2014 5:30:59 AM 8/31/2014 5:36:26 AM 0
Ok, this should select the last of any Down or Up event before the next of the opposite type (last Down before an Up, and last Up before a Down) which I think is what you wanted. Basically match each time the system goes down, with the time it came back up, using the last uptime/downtime notification if there are multiple in a row. I think your whole 'Discard the first of any sequential duplicate event type' is in error, but this does exactly that.
Event collection duplicated from Noah Sparks with the Select argument trimmed down a bit. Then it goes into a Switch creating a custom object whenever there is a Downtime event, and then setting the uptime for each uptime event, and outputting it whenever the next downtime event happens and starting over again.
$Events = Get-EventLog -LogName system |
Where-Object { $_.eventid -eq 6005 -OR $_.eventID -eq 6006 -OR $_.eventID -eq 6008 } |
Select timegenerated, eventid | sort-object timegenerated
$record=$null
$Output = Switch($Events){
{$_.EventID -match "600(6|8)"}{if(![string]::IsNullOrEmpty($Record.up)){$record}
$Record=[pscustomobject][ordered]#{
'Down'=$_.timegenerated
'Up'=$null
'Expected'=If($_.EventID -eq 6006){$true}else{$false}
}}
{$_.EventID -eq "6005"}{$Record.Up = $_.TimeGenerated}
}
$Output += $record
$Output
Personally I'd go with the first uptime even after a downtime event happens, but that's just me. That would take some different coding. Anyway, the results of that look like this (using my own event log entries):
Down Up Expected
---- -- --------
11/20/2013 8:47:42 AM 11/20/2013 8:49:11 AM True
11/20/2013 3:50:14 PM 12/13/2013 9:14:52 AM True
12/13/2013 9:26:21 AM 12/13/2013 9:27:42 AM False
12/13/2013 3:40:07 PM 12/13/2013 3:41:31 PM True
1/9/2014 1:13:31 PM 1/16/2014 12:38:08 PM True
1/16/2014 12:39:21 PM 1/16/2014 12:48:44 PM True
If you'll look at the second listing there, I know my computer was not down from 11/20 to 12/13, but discarding the first of sequential duplicates that is how it comes across. If we didn't do that it would show the system coming back up just one minute after going down, which is much more likely for my computer.
Here is an answer for how to do it that should skip duplicate entries. However, you will likely want to adjust the events you are querying otherwise you end up with down/up conditions occurring at the same time.
$Events = Get-EventLog -LogName system |
Where-Object { $_.eventid -eq 6005 -OR $_.eventID -eq 6006 -OR $_.eventID -eq 6008 } |
Select timegenerated, eventid, message, index, recordid | sort-object timegenerated
[array]$FullObj = [pscustomobject] #{
'Time' = ''
'Status' = ''
'Message' = ''
}
Foreach ($Event in $Events)
{
If ($Event.Message -eq 'The Event log service was started.' -and $fullobj.status[-1] -ne 'UP')
{
[array]$FullObj += [pscustomobject] #{
'Time' = $Event.TimeGenerated
'Status' = 'UP'
'Message' = $Event.Message
}
}
Elseif ($fullobj.status[-1] -eq 'UP' -and $fullobj.Message[-1] -notlike "The previous system shutdown*")
{
[array]$FullObj += [pscustomobject] #{
'Time' = $Event.TimeGenerated
'Status' = 'DOWN'
'Message' = $Event.Message
}
}
}
$FullObj | select time,status
Related
I'm creating a PowerShell script for DPM which outputs offsite ready tapes. The below code takes a few seconds to complete the DPM connection and to return the query, which is fine.
# Connect to local DPM server
$DPMServer = $env:COMPUTERNAME
Connect-DPMServer -DPMServerName $DPMServer | Out-Null
# Get the DPM libary
$DPMLib = Get-DPMLibrary
#Format output
$Formatting = #{Expression={$_.Barcode}; Label="Barcode "; Width=12},
#{Expression={"{0:MM/dd/yyyy HH:mm tt}" -f $_.CreationDate}; Label="Creation Date "; Width=23},
#{Expression={$_.DisplayString}; Label="Tape Label "; Width=28},
#{Expression={"{0,15:N0}" -f $_.DataWrittenDisplayString}; Label="Data Written "}
#Calculate Monday at midnight of this week
$Monday = (Get-Date).AddDays((-1 * (Get-Date).DayOfWeek.Value__) + 1).Date
# Get specific tapes
$Tapes = Get-DPMTape -DPMLibrary $DPMLib |
Where-Object {$_.Barcode -notlike "CLN*"} |
Where-Object {$_.DisplayString -notlike "Free"} |
Where-Object {$_.CreationDate -gt $Monday} |
Sort-Object Barcode |
Format-Table -Property $Formatting
Write-Host "`nOffsite Ready Tapes:" -ForegroundColor Cyan
#Output tapes
$Tapes
This outputs like so:
My question is how do I now select just the barcodes listed within the $Tapes array, and output them (below what I already have) as a comma delimited list, without running the DPM query again. I've tried all sorts of things with no luck, I'm missing something obvious.
If I do a second connection to DPM, I can do it like this, but I'm trying to avoid doubling the time it takes to run. This is part of my original newbie script that I'm trying to better.
# Define DPM server to connect to
$DPMServer = $env:COMPUTERNAME
Connect-DPMServer -DPMServerName $DPMServer | Out-Null
# Get the DPM libary
$DPMLib = Get-DPMLibrary
# Get tape display strings and barcodes, sort by barcode
$Tapes = Get-DPMTape -DPMLibrary $DPMLib | Select-Object CreationDate, DisplayString, Barcode | Sort-Object Barcode
# Create empty array
$DPMTapesForOffsite = #()
Write-Host "`nOffsite Ready Tapes:`n" -ForegroundColor Cyan
foreach ($_ in $Tapes) {
# Exclude cleaning tapes
if ($_.Barcode -notlike "CLN*") {
# Exclude marked as free
if ($_.DisplayString -notlike "Free") {
$TimeStamp = Get-Date $_.CreationDate
# Timestamp is from this week
if ($Timestamp -gt $Monday) {
$DPMTapesForOffsite = $DPMTapesForOffsite + $_.barcode
Write-Host $_.barcode
}
}
}
}
# Format tape list as comma delimited
$DPMTapesForOffsite = $DPMTapesForOffsite -join ","
I'm missing some obvious, any help would greatly be appreciated.
# Omit Format-Table initially, so as to store actual *data* in $tapes,
# not *formatting instructions*, which is what Format-* cmdlets return.
$tapes = Get-DPMTape -DPMLibrary $DPMLib |
Where-Object {$_.Barcode -notlike "CLN*"} |
Where-Object {$_.DisplayString -notlike "Free"} |
Where-Object {$_.CreationDate -gt $Monday} |
Sort-Object Barcode
# Now you can apply the desired formatting.
Write-Host "`nOffsite Ready Tapes:" -ForegroundColor Cyan
$tapes | Format-Table -Property $Formatting
# Thanks to PowerShell's member-access enumeration feature,
# accessing property .Barcode on the entire $tapes *collection*
# conveniently returns its *elements'* property values.
# -join ',' joins them with commas.
$tapes.Barcode -join ','
Format-* cmdlets output objects whose sole purpose is to provide formatting instructions to PowerShell's output-formatting system - see this answer.
In short: only ever use Format-* cmdlets to format data for display, never for subsequent programmatic processing.
This answer explains member-access enumeration.
I'm trying to find the row with an attribute that is larger than the other row's attributes. Example:
$Array
Name Value
---- ----
test1 105
test2 101
test3 512 <--- Selects this row as it is the largest value
Here is my attempt to '1 line' this but It doesn't work.
$Array | % { If($_.value -gt $Array[0..($Array.Count)].value){write-host "$_.name is the largest row"}}
Currently it outputs nothing.
Desired Output:
"test1 is the largest row"
I'm having trouble visualizing how to do this efficiently with out some serious spaghetti code.
You could take advantage of Sort-Object to rank them by the property "Value" like this
$array = #(
[PSCustomObject]#{Name='test1';Value=105}
[PSCustomObject]#{Name='test2';Value=101}
[PSCustomObject]#{Name='test3';Value=512}
)
$array | Sort-Object -Property value -Descending | Select-Object -First 1
Output
Name Value
---- -----
test3 512
To incorporate your write host you can just run the one you select through a foreach.
$array | Sort-Object -Property value -Descending |
Select-Object -First 1 | Foreach-Object {Write-host $_.name,"has the highest value"}
test3 has the highest value
Or capture to a variable
$Largest = $array | Sort-Object -Property value -Descending | Select-Object -First 1
Write-host $Largest.name,"has the highest value"
test3 has the highest value
PowerShell has many built in features to make tasks like this easier.
If this is really an array of PSCustomObjects you can do something like:
$Array =
#(
[PSCustomObject]#{ Name = 'test1'; Value = 105 }
[PSCustomObject]#{ Name = 'test2'; Value = 101 }
[PSCustomObject]#{ Name = 'test3'; Value = 512 }
)
$Largest = ($Array | Sort-Object Value)[-1].Name
Write-host $Largest,"has the highest value"
This will sort your array according to the Value property. Then reference the last element using the [-1] syntax, then return the name property of that object.
Or if you're a purist you can assign the variable like:
$Largest = $Array | Sort-Object Value | Select-Object -Last 1 -ExpandProperty Name
If you want the whole object just remove .Name & -ExpandProperty Name respectively.
Update:
As noted PowerShell has some great tools to help with common tasks like sorting & selecting data. However, that doesn't mean there's never a need for looping constructs. So, I wanted to make a couple of points about the OP's own answer.
First, if you do need to reference array elements by index use a traditional For loop, which might look something like:
For( $i = 0; $i -lt $Array.Count; ++$i )
{
If( $array[$i].Value -gt $LargestValue )
{
$LargestName = $array[$i].Name
$LargestValue = $array[$i].Value
}
}
$i is commonly used as an iteration variable, and within the script block is used as the array index.
Second, even the traditional loop is unnecessary in this case. You can stick with the ForEach loop and track the largest value as and when it's encountered. That might look something like:
ForEach( $Row in $array )
{
If( $Row.Value -gt $LargestValue )
{
$LargestName = $Row.Name
$LargestValue = $Row.Value
}
}
Strictly speaking you don't need to assign the variables beforehand, though it may be a good practice to precede either of these with:
$LargestName = ""
$LargestValue = 0
In these examples you'd have to follow with a slightly modified Write-Host command
Write-host $LargestName,"has the highest value"
Note: Borrowed some of the test code from Doug Maurer's Fine Answer. Considering our answers were similar, this was just to make my examples more clear to the question and easier to test.
Figured it out, hopefully this isn't awful:
$Count = 1
$CurrentLargest = 0
Foreach($Row in $Array) {
# Compare This iteration vs the next to find the largest
If($Row.value -gt $Array.Value[$Count]){$CurrentLargest = $Row}
Else {$CurrentLargest = $Array[$Count]}
# Replace the existing largest value with the new one if it is larger than it.
If($CurrentLargest.Value -gt $Largest.Value){ $Largest = $CurrentLargest }
$Count += 1
}
Write-host $Largest.name,"has the highest value"
Edit: its awful, look at the other answers for a better way.
I am trying to locate discrepancies in BIND DNS records. I would like to output a CSV file that only has those discrepancies. I have a CSV file that has all records from all locations in BIND (ns.prvt, ns.pub, common, includes). What I'm trying to figure out is how to output a CSV that only shows the discrepancies. For 2 records to be considered a discrepancy, they must meet the following criteria:
Both records have the same RecordName and RecordType.
Both records have different Data or TTL.
Both records come from different locations.
I am almost there with the following script but it keeps showing me a couple of rows that don't necessarily meet the above criteria.
$Records = Import-Csv C:\Temp\Domain_ALL.csv | Select * | Sort Data,Location
$RecordsRev = #()
$Records | % {
$Record = $_
$Records | % {
$DataFE = $_
If (
([string]($Record | ? {($_.RecordName -eq $DataFE.RecordName)}).RecordName -eq $DataFE.RecordName) -and
([string]($Record | ? {($_.RecordName -eq $DataFE.RecordName)}).RecordType -eq $DataFE.RecordType) -and
([string]($Record | ? {($_.RecordName -eq $DataFE.RecordName)}).Location -ne $DataFE.Location) -and
(([string]($Record | ? {($_.RecordName -eq $DataFE.RecordName)}).Data -ne $DataFE.Data) -or
([string]($Record | ? {($_.RecordName -eq $DataFE.RecordName)}).TTL -ne $DataFE.TTL))
) {
$RecordsRev += $_
}
}
}
$RecordsRev | Export-Csv C:\Temp\Domain_Discrepancies.csv -NoType
The results that I get are:
RecordName RecordType Data TTL Location
---------- ---------- ---- --- --------
domain.com TXT "MS=abc1234566" 600 Includes
domain.com TXT "MS=abc1234566" 600 Common
domain.com TXT "site-verification=abcd1234" 600 Includes
domain.com TXT "site-verification=abcd1234" 600 Common
www CNAME somedomain.com.test. 600 Includes
www CNAME somedomain.com. 600 Common
The results that I expect are:
RecordName RecordType Data TTL Location
---------- ---------- ---- --- --------
www CNAME somedomain.com.test. 600 Includes
www CNAME somedomain.com. 600 Common
How do I delete all duplicated rows in the array? This is different from "Select * -unique" as I don't want to keep any row that contains the duplicated information.
EDIT: I think the main problem is that, since the script checks each record against every record in the CSV, it technically is a discrepancy. For example, in the below table, record 1 meets the criteria to be a discrepancy because it differs from record 4. However, since record 1 is the same as record 2, it should actually be omitted from the results.
RecordNumber RecordName RecordType Data TTL Location
------------ ---------- ---------- ---- --- --------
1 domain.com TXT "MS=abc1234566" 600 Includes
2 domain.com TXT "MS=abc1234566" 600 Common
3 domain.com TXT "site-verification=abcd1234" 600 Includes
4 domain.com TXT "site-verification=abcd1234" 600 Common
5 www CNAME somedomain.com.test. 600 Includes
6 www CNAME somedomain.com. 600 Common
Any help would be greatly appreciated.
Kyle
I was able to figure this out with the help of someone who deleted their post... Here is the script that I am using now to find all records that meet ALL of the following criteria:
Both records have the same RecordName and RecordType. -AND
Both records have different Data or TTL. -AND
Both records come from different locations.
$Records = Import-Csv C:\Temp\Domain_ALL.csv | Select * | Sort Data,Location
$Discrepancies = #()
$GoodRecords = #()
$BadRecords = #()
$Records | ForEach-Object {
# for each record $_, compare it against every other record..
foreach ($R in $Records) {
# if Both records have the same RecordName and RecordType..
if (($_.RecordName -eq $R.RecordName) -and ($_.RecordType -eq $R.RecordType)) {
# and if Both records come from different locations..
if ($_.Location -ne $R.Location) {
# if Both records have the same Data and TTL then they are considered good:
if (($_.Data -eq $R.Data) -and ($_.TTL -eq $R.TTL)) {
$GoodRecords += $_
}
Else{
# if Both records have different Data or TTL then they are considered bad:
$BadRecords += $_
}
}
}
}
}
ForEach ($BadRecord in $BadRecords){
If (($GoodRecords -notcontains $BadRecord)){
$Discrepancies += $BadRecord
}
}
$Discrepancies | Select * -Unique | Sort RecordName,Location,Data | ft
I'm currently having a bit of trouble with my current project. I have two arrays - the first array contains reference values for disk size:
$RequiredDisks0 = New-Object System.Object
$RequiredDisks0 | Add-Member -Type NoteProperty -Name "DeviceID" -Value "C:"
$RequiredDisks0 | Add-Member -Type NoteProperty -Name "SizeGB" -Value "80"
The second array contains the disk information of the underlying system:
$SystemDisks = Get-WmiObject Win32_LogicalDisk |
Where {$_.DriveType -eq 3} |
select DeviceID,
#{Name="Size(GB)";Expression={[decimal]("{0:N0}" -f ($_.Size/1gb))}}
What I would like to do, is check the given array against the reference array to see if any given disks are smaller than required. I've found out that I can compare the arrays by using
Compare-Object -ReferenceObject $RequiredDisks -DifferenceObject $SystemDisks -Property SizeGB,DeviceID
And I indeed receive the differences as follows:
SizeGB DeviceID SideIndicator
------ -------- -------------
99 C: =>
15 H: =>
100 I: =>
80 C: <=
25 H: <=
200 I: <=
Where I'm having trouble is working with the output. The result I'd like to achieve is an output stating "Disk n is smaller than required!". I know that everything with the side indicator "<=" is the required value and everything with the "=>" side indicator is the given value. I've tried a foreach statement but I am unable to process the data as needed - I need to check the given value against the required value and if it's smaller, tell me so. How can I again compare these values as required? Basically a "foreach object where SideIndicator is <= compare to object where SideIndicator is => and DeviceID equals DeviceID". How do I translate that into proper code?
It looks to me like the Compare-Object is doing a double comparison on both properties. The documentation or another StackOverflow soul may be able to help with that command.
My approach would be to translate your pseudo-code into code:
foreach ($disk in $SystemDisks){
$ref = $RequiredDisks | Where-object {$_.DeviceID -eq $disk.DeviceID}
if([int]($disk.SizeGB) -lt [int]($ref.SizeGB){
Write-Output "Disk $($disk.DeviceID) is smaller than required!"
}
}
I have a .csv with a few hundred records that I need to dissect into several different files. There is a part of the code that takes an array of objects and filters the file based on the array. It works great for the part that finds things equal to whats in the array, but when I try to filter based on whats not contained in the array it ignores any version of a "not equal" operator I can find. I think it has something to do with the data type, but can't figure why it would make a difference when the equal operator works.
CSV File
"Number","Ticket","Title","Customer User","CustomerID","Accounted time","Billing"
"1","2014041710000096","Calendar issues","george.jetson","Widget, Inc","0.25","Labor",""
"2","2014041710000087","Redirected Folder permission","jane.smith","Mars Bars, Inc.","1","Labor",""
"3","2014041610000203","Completed with No Files Changed ""QB Data""","will.smith","Dr. Smith","0","Labor",""
PowerShell Code
$msaClients = #("Widget, Inc","Johns Company")
$billingList = import-csv "c:\billing\billed.csv"
$idZero = "0"
$msaArray = foreach ($msa in $msaClients) {$billingList | where-object {$_.CustomerID -eq $msa -and $_."Accounted time" -ne $idZero}}
$laborArray = foreach ($msa in $msaClients) {$billingList | where-object {$_.CustomerID -ne $msa -and $_."Accounted time" -ne $idZero}}
$msaArray | export-csv c:\billing\msa.csv -notypeinformation
$laborArray | export-csv c:\billing\labor.csv -notypeinformation
I have tried all the different logical operators for the not equal and it just seems to ignore that part. I have much more to the code if something doesn't seem right.
What am I missing, and Thanks in advance for any help!
If i understand this correct, you want the values in $msaArray, where $billingList contains customerIDs which are present in $msaClients but their corresponding Accounted time should not be eual to $idzero( 0 in this case)
PS C:\> $msaArray = ($billingList | where {(($msaclients -contains $_.customerid)) -and ($_.'accounted time' -ne $idzero)})
PS C:\> $msaArray | ft -auto
Number Ticket Title Customer User CustomerID Accounted time Billing
------ ------ ----- ------------- ---------- -------------- -------
1 2014041710000096 Calendar issues george.jetson Widget, Inc 0.25 Labor
And for $laborArray, where $billingList does not contain customerIDs which are present in $msaClients and their corresponding Accounted time should not be eual to $idzero as well( 0 in this case)
PS C:\> $laborArray = ($billingList | where {(!($msaclients -contains $_.customerid)) -and ($_.'accounted time' -ne $idZero)})
PS C:\> $laborArray | ft -auto
Number Ticket Title Customer User CustomerID Accounted time Billing
------ ------ ----- ------------- ---------- -------------- -------
2 2014041710000087 Redirected Folder permission jane.smith Mars Bars, Inc. 1 Labor
Your -ne operator is working, but you are looping too many times in $msaclients to get $laborArray.i.e, when $msa = "Widget, Inc", you got "Mars Bars, Inc." as output, but foreach loop ran again and $msa value changed to "Johns Company" and in this case you got "Mars Bars, Inc." and "Widget, Inc" as output too. Hence you ended up with three outputs.