If you would like a complete breakdown of the script and the results I have obtained so far, you can reference my previous post.
Import the items from the pipeline in to an array
I am trying to figure out how to get only the two fields I need from the array, so I can manipulate the data. Right now the array shows the entire line as the first element in the array.
$Date = (Get-Date -format "MM-dd-yyyy")
$DateTime = (Get-Date)
$Projects = Import-Csv c:\temp\pm_project.csv
#Show me the entire list of Projects
$Projects
$TS_Entries = Get-Content "c:\temp\timesheet\$Date.txt"
#Show me all the chosen entries in the text file
$TS_Entries
#Show me only the lines in the text file that match the regex statement
$TS_Entries = Select-String -Path "C:\temp\TimeSheet\$Date.txt" -Pattern '(?>FOAH|PRJ)\d{1,10}' -allmatches | Select-Object -expand matches | Select-Object -expand Value
$TS_Entries
$TS_Entries | group -NoElement
I cannot seem to utilize the count element in the array. I need to get a value of each value in Count and multiply them by 15. The only thing I can reference in the list below is Name
Count Name
----- ----
2 FOAH278
1 FOAH519
1 FOAH704
3 FOAH718
2 FOAH780
So i'm not sure i fully understand what you are asking but here goes:
So say you have a txtfile :
C:\temp\TimeSheet\11-06-2017.txt:1:11/06/2017 18:45:56 - This 15 minutes is dedicated to PROJECT 100 FOAH18
C:\temp\TimeSheet\11-06-2017.txt:2:11/06/2017 18:45:58 - This 15 minutes is dedicated to PROJECT 90 FOAH278
C:\temp\TimeSheet\11-06-2017.txt:3:11/06/2017 18:45:59 - This 15 minutes is dedicated to PROJECT 80 FOAH313
C:\temp\TimeSheet\11-06-2017.txt:4:11/06/2017 18:46:00 - This 15 minutes is dedicated to PROJECT 70 PRJ0031905
C:\temp\TimeSheet\11-06-2017.txt:5:11/06/2017 18:46:02 - This 15 minutes is dedicated to PROJECT 60 PRJ0031909
C:\temp\TimeSheet\11-06-2017.txt:6:11/06/2017 18:46:03 - This 15 minutes is dedicated to PROJECT 50 PRJ0032045
The following script uses .NET regex class
$test = gc "C:\temp\stack.txt"
$m = [regex]::Matches($test,'(?msi)((?<=Project )\d{0,}).*?(FOAH|PRJ)(\d{1,10})')
Example of regex: https://regex101.com/r/yhgl9v/1
foreach($match in $m){
write-host "ID:" $match.Groups[1].Value
write-host "Prefix:" $match.Groups[2].Value
write-host "Number:" $match.Groups[3].Value
""
}
This will give you:
ID: 100
Prefix: FOAH
Number: 18
ID: 90
Prefix: FOAH
Number: 278
ID: 80
Prefix: FOAH
Number: 313
ID: 70
Prefix: PRJ
Number: 0031905
ID: 60
Prefix: PRJ
Number: 0031909
ID: 50
Prefix: PRJ
Number: 0032045
Is it this you are looking for?
$test = gc "C:\temp\stack.txt"
$m = [regex]::Matches($test,'(?msi)((?<=Project )\d{0,}).*?(FOAH|PRJ)(\d{1,10})')
$arr=#()
foreach($match in $m){
$arr += [PSCustomObject]#{
id = $match.Groups[1].Value
prefix = $match.Groups[2].Value
number = $match.Groups[3].Value
}
}
$arr | Export-Csv C:\temp\stock.csv -NoTypeInformation -Delimiter ';'
So you create a customobject (hashtable) that works with a value-index system.
Once you've filled up your array, you can later on (outside the loop), export it into a csv-file.
Your CSV-file will contain:
"id";"prefix";"number"
"100";"FOAH";"18"
"90";"FOAH";"278"
....
Related
I am trying to work with a large array. Each element within the array is taken from a line of a SQL table and has multiple objects. Each line from the SQL table is a time entry, and included a User ID, a start time, and an end time. I am trying to see if in a given data set (which will be one day’s worth of data) a particular User ID has logged at least 8 hours total.
For example, a small section of the array (named $Array) might look like this:
UserID: 1
StartTime: 8:00 AM
EndTime: 10:00 AM
UserID: 2
StartTime: 8:00 AM
EndTime: 10:30 AM
UserID: 3
StartTime: 9:00 AM
EndTime: 10:00 AM
UserID: 1
StartTime: 10:00 AM
EndTime: 01:00 PM
UserID: 3
StartTime: 10:00 AM
EndTime: 04:00 PM
And so on. How can I loop through each element and tally up the allocated time for each the user ID?
Calculating the total time per entry is easy enough:
#Calculate entry length
ForEach ($element in $array)
{
$EntryStart = $element.StartTime
$EntryEnd = $element.EndTime
$EntryLength = $EntryEnd-$EntryStart
}
From this, I can successfully calculate the time of the entry via $EntryLength. I am struggling to add the value of $EntryLength to an overall timekeeping variable based on the UserID. How can I dynamically change the name of the timekeeping variable I am adding the $entrylength value to within each loop?
So far, I have tried this without success.
#Find all User IDs in recent Entries
ForEach ($Element in $Array)
{
$UserID = $Element.UserID
$userIDs += "$UserID"
}
#Remove Duplcate IDs from array
$UniqueUserIDs = $userIDs | select -Unique
#Create unique variable for each user ID
ForEach ($UniqueUserID in $UniqueUserIDs)
{
New-Variable = -Name "TimeFrom$UniqueUserID"
}
This last part produces variables from each user ID, such as
$TimeFrom1
$TImeFrom2
$Timefrom3
I tried dynamically creating the text which matches the name of the global timekeeping variable (e.g. 'TimeFrom1') within the loop, and using that to add each element's $EntryLength, but this isn't working.
#Add time to timekeeping variables
ForEach ($element in $array)
{
$UserID = $element.UserID
$EntryStart = $element.Starttime
$EntryEnd = $element.Endtime
$EntryLength = $EntryEnd-$EntryStart
$LoopTimeKeepingVariable = "TimeFrom$userID"
$"$LoopTimeKeepingVariable" += $EntryLength
}
Is there a better way to tally up all of the time for each user ID?
Here is all of the code in one go in case that is helpful:
#Find all User IDs in recent Entries
ForEach ($Element in $Array)
{
$UserID = $Element.UserID
$userIDs += "$UserID"
}
#Remove Duplicate IDs from array
$UniqueUserIDs = $userIDs | select -Unique
#Create unique variable for each user ID
ForEach ($UniqueUserID in $UniqueUserIDs)
{
New-Variable = -Name "TimeFrom$UniqueUserID"
}
#Add time to timekeeping variables
ForEach ($element in $array)
{
$UserID = $element.UserID
$EntryStart = $element.StartTime
$EntryEnd = $element.EndTime
$EntryLength = $EntryEnd-$EntryStart
$LoopTimeKeepingVariable = "TimeFrom$userID"
$"$LoopTimeKeepingVariable" += $EntryLength
}
I am in no way set on using unique variables to achieve this task, and graciously welcome any alternative methods to achieve the intended purpose.
You can do the following, which will output the UserID values with 8 or more total hours:
$array | Group-Object UserID | Foreach-Object {
$TimeSpans = $_.Group | Foreach-Object { New-TimeSpan $_.StartTime $_.EndTime }
if ([System.Linq.Enumerable]::Sum([double[]]$TimeSpans.TotalHours) -ge 8) {
$_.Name
}
}
If you only need to know the UserID value if one specific time span is 8 hours or more, you can simply modify the code above to not perform a sum:
$array | Group-Object UserID | Foreach-Object {
$TimeSpans = $_.Group | Foreach-Object { New-TimeSpan $_.StartTime $_.EndTime }
if ($TimeSpans.TotalHours -ge 8) {
$_.Name
}
}
EDIT: How To Detect 0 Time For User Not in the Table
$UserIDs = 1,2,3,4,5,6
$AllUsers = $array + ($UserIDs | Where {$_ -notin $array.UserID} | Foreach-Object {
[pscustomobject]#{UserID = $_; StartTime = 0; EndTime = 0}
})
$AllUsers | Group-Object UserID | Foreach-Object {
$TimeSpans = $_.Group | Foreach-Object { New-TimeSpan $_.StartTime $_.EndTime }
if ($TimeSpans.TotalHours -lt 8) {
$_.Name
}
}
Explanation:
$TimeSpans will contain every time span for the current UserID value. At this point, you can choose to sum all the TotalHours values for that UserID or use a comparison operator -ge (greater than or equal to) to determine if any time span is 8 or more hours.
Using Group-Object on the UserID property will group all objects with matching UserID values into one object. The Name property of that grouping will contain the UserID grouped value. The Group property will contain a collection of the objects with matching UserID values.
New-TimeSpan creates a time span between start and end times. The returned object contains time-related properties including TotalHours.
Using LINQ's Sum() method, we can easily sum an array of equal numeric types.
My question is two fold I'm playing around with PS and trying new stuff, I wrote a small script that generates full names by exporting first names from a txt.file and last names from another txt.file and finally randomly combines the 2 to create a full name, this is the script:
$Firstnames = Get-Content -Path 'C:\Users\user\Desktop\Firstname.txt'
$Lastnames = Get-Content -Path 'C:\Users\user\Desktop\Lastnames.txt'
$feminine = $firstnames | ?{$_ -like "*;frau"} | %{$_.Split(';')[0]}
Write-Host "Random full name generator"
$numberofNames = 1..(Read-Host 'how many combination do you want to generate?')
$numberofNames | foreach {
$f = $feminine[ (Get-Random $feminine.count)]
$l = $Lastnames[ (Get-Random $Lastnames.count)]
$full = $f+" "+$l
Write-output $full
}
1- now $numberofNames is an array 'a range operator', I would like to know what bad consequences could use this method have? is this the best approach for the users input?
2- what is the key difference between using for example $a = 100 and $a = 1..100?
in case you need to know:
$firstnames looks something like this:
Linda;frau
Aline;frau
Lisa;frau
Katja;frau
Karen;frau
and $lastnames:
smith
anderson
müller
klein
Rex
thank you
1- now $numberofNames is an array 'a range operator', i would like to know what bad consequences could using this method have? is this the best approach for the users input?
It's valid. Even if the user adds e.g. a string as input Powershell will throw an error that it could not cast the input to an int:
1 .. "test" | % {Write-Host $_ }
Cannot convert value "test" to type "System.Int32". Error: "Input string was not in a correct format."
At line:1 char:1
+ 1 .. "test" | % {Write-Host $_ }
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [], RuntimeException
+ FullyQualifiedErrorId : InvalidCastFromStringToInteger
Even a neg. int shouldn't be a problem since you're not using the array content for any indexing operations.
2- what is the key difference between using for example $a = 100 and $a = 1..100?
$a = 100 points to integer with the value 100.
$a = 1..100 is an array with 100 entries.
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
Code:
$arr1 = "" | select blabla,blabla2
$arr2 = "" | select blabla3,blabla4
$arrtotal = #()
$arrtotal += $arr1
$arrtotal += $arr2
$arrtotal
Printout:
blabla blabla2
However, when attempting to print both cells individually (not one after the other but simply selecting in PS ISE and hitting F8):
$arrtotal[0]
blabla blabla2
$arrtotal[1]
blabl3 blabla4
EDITED:
I would have expected both array columns to be printed when printing $arrtotal. Not just one of them. Further more it's unclear to me why printing them individually works but one after the other i.e "$arrtotal[0];$arrtotal[1]" does not.
EDIT2:
This is my original code.
All it does is query Sparkpost's API in order to build a custom HTML report.
$test = (Invoke-WebRequest "https://api.sparkpost.com/api/v1/metrics/deliverability?metrics=count_injected,count_sent,count_bounce,count_accepted&from=2016-01-01T08:00&to=2016-04-25T08:00" -Headers #{"Authorization"="xxxxxxxxxxxxx";"Content-Type"= "application/json"}).content | ConvertFrom-Json
$fill1 = "" | select EmailsReceived,EmailsSent,EmailsBounced
$fill1.EmailsReceived = $test.results.count_injected
$fill1.EmailsSent = $test.results.count_accepted
$fill1.EmailsBounced = $test.results.count_bounce
$fill2 = "" | select DeliveredPrecentage,BouncesPrecentage
$fill2.DeliveredPrecentage = [math]::round($test.results.count_accepted/$test.results.count_injected*100,2)
$fill2.BouncesPrecentage = [math]::round(($test.results.count_bounce)/$test.results.count_accepted*100,2)
$arr = #()
$arr += , $fill1
$arr += , $fill2
My problem is that I cant simply convert $arr into an HTML file like I've done numerous times before.
$arr
EmailsReceived EmailsSent EmailsBounced
107 107 12
On the other hand
$arr | Format-List
EmailsReceived : 107 EmailsSent : 107 EmailsBounced : 12
DeliveredPrecentage : 100 BouncesPrecentage : 11.21
I'd like to make an HTML out of everything so I can send it via email later. How can I pipe it all?
Its because $arr1and $arr2are two different PSCustomObjects. You can print the whole arrayin a list using the Format-List cmdlet:
$arrtotal | Format-List
Output:
blabla :
blabla2 :
blabla :
blabla2 :
Answer to your edit:
This looks like a single record for me, try this:
$record = [PsCustomObject]#{
EmailsReceived = $test.results.count_injected
EmailsSent = $test.results.count_accepted
EmailsBounced = $test.results.count_bounce
DeliveredPrecentage = [math]::round($test.results.count_accepted/$test.results.count_injected*100,2)
BouncesPrecentage = [math]::round(($test.results.count_bounce)/$test.results.count_accepted*100,2)
}
$record | convertto-html | out-file file.html
Note: I also changed the logic to create the object to a more well known approach using a PsCustomObject typecast on a hashtable.
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