How to check if the Array date is in specific pattern - powershell

I am able to check a single date using below working code.
$date = "01/APR/2010:10:10:10"
$format = 'dd/MMM/yyyy:HH:mm:ss'
if(![datetime]::TryParseexact($date,$format,[system.Globalization.DateTimeFormatInfo]::InvariantInfo,[system.Globalization.DateTimeStyles]::None, [ref]$result ))
{
echo "Date format failed"
}
But I am unable to verify if the dates are in an array, like this
$date = #("01/APR/2010:10:10:10","01/MAY/2010:10:10:10")

You just need to iterate through dates one by one.
$dates = #("01/APR/2010:10:10:10","01/MAY/2010:10:10:10")
foreach ($date in $dates) {
# add verification code here
}

Related

Iterate through CSV and create an array

I am newbie to Powershell. Need a logic for CSV automation. I have a CSV log file contains large number of API calls.
I need to go row by row and segregate the data, output should be like below. Sum of calls count and average of response time to be updated.
I have written complicated If else conditions for different types of API calls and able to take the scenario name and other values from the csv. My pain starts here, struggling to come to conclusion to move forward. Can i create an array and store all the values then do all the calculation later or write the values in another csv then do all the calculation to find the Count and average response time?
If i choose array, scenario should not be duplicated. For me its really hard to take a decision without knowing the available cmdlets for array and CSV. Please throw some light..
Thanks in advance...
Here is an approach you can use a combination of c# available to Powershell (which can be MUCH more efficient handling larger files and data).
The first component is you need some consistent logic to isolate the API category you want each URL to be assigned. From your screenshots, sometimes it seems you use last segment of the URL but others it is some path in the middle of the resource.
Here is just a quick approach where you pass in an array of categories, and if it can be matched to URI in any way, then that category is used. Otherwise, the URI stands as its own category. Please replace with whatever logic you want here.
function Get-ApiCategory {
param([string[]] $Categories, [string] $Text)
foreach ($c in $Categories) {
if ($Text.IndexOf($c) -gt 0) {
return $c
}
}
return $Text # Not found
}
Then, here is a method that (1) reads the large CSV file row-by-row and uses basic parsing logic (since your source data seems simple enough) without loading the full file into memory, and then (2) exports a CSV file with summary data.
function Write-SummaryToFile {
param([string[]] $Categories, [string] $InputFile, [string] $Output)
# Parse the file line-by-line (optimize for memory)
$result = #{}
$lineNum = 0
Write-Host $InputFile
foreach ($line in [System.IO.File]::ReadLines($InputFile)) {
if ($lineNum++ -lt 1) { continue } # Skip header
$cols = $line.Split(',')
$category = Get-ApiCategory $Categories $cols[0]
$new = #{
Category = $category
Count = [int]$cols[1]
AvgResponse = [double]$cols[2]
}
if ($result.ContainsKey($category)) {
$weighted = $result[$category].AvgResponse * $result[$category].Count
$result[$category].Count += $new.Count
$result[$category].AvgResponse = ($weighted + $new.AvgResponse * $new.Count) / $result[$category].Count;
} else {
$result[$category] = $new
}
}
# Output to file
if (Test-Path $Output) { Remove-Item $Output }
try {
$stream = [System.IO.StreamWriter] $Output
$stream.WriteLine('Scenario,Count,Avg_Response_Time')
$result.Values | ForEach-Object { $stream.WriteLine([string]::Format("{0},{1},{2}", $_.Category, $_.Count, $_.AvgResponse.ToString("0.##"))) }
}
finally {
$stream.Dispose()
}
}
Then, you are able to call these methods in an example like this:
$categories = #('MoveRequestQueue', 'DeliveryDate')
Write-SummaryToFile $categories 'c:\dev\scratch\ps1\test.csv' 'C:\dev\scratch\ps1\Output.csv'

How to avoid errors when casting null REST data to datetime

I am using a REST API to pull invoice data, from an external company, into an Excel file using PowerShell. I can pull the data just fine using the Invoke-RestMethod. Although, when it pulls the datetime fields, it is in a format I do not prefer. To automate my job as much as possible, is there a way to cast the data to datetime. Keep in mind, not every field has data so there are null fields which cause the errors. I basically want to pull all data, if it has an ugly-formatted datetime, change it to the way I want to see it, or if it is empty, just continue on.
I have tried [nullable[datetime]] (found it on google somewhere), but that throws other errors. I have tried using an 'IF' but that also generates in out set of syntax errors.
$out = Invoke-RestMethod -Uri $assetsURL
#$out.Invoices | Export-Excel $outputcsv
$out.Invoices |
Select-Object * |
ForEach-Object {
$Properties = [ordered]#{
InvoiceNumber = $_.InvoiceNumber
DueDate = $_.DueDate #[datetime]::parseexact($_.DueDate, 'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
Currency = $_.Currency
TotalDue = $_.TotalDue
PeriodFrom = $_.PeriodFrom #[datetime]::parseexact($_.PeriodFrom,'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
PeriodTo = $_.PeriodTo #[datetime]::parseexact($_.PeriodTo,'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
BillingName = $_.BillingName
BillingAddress = $_.BillingAddress
BillingAttentionTo = $_.BillingAttentionTo
BillingCity = $_.BillingCity
BillingState = $_.BillingState
BillingPostalCode = $_.BillingPostalCode
RemitToName = $_.RemitToName
RemitToAddress = $_.RemitToAddress
RemitToAttentionTo = $_.RemitToAttentionTo
RemitToCity = $_.RemitToCity
RemitToState = $_.RemitToState
RemitToPostalCode = $_.RemitToPostalCode
Lease = $_.Lease
Schedule = $_.Schedule
CreatedDate = $_.CreatedDate #[datetime]::parseexact($_.CreatedDate, 'yyyy-MM-ddThh:mm:ss', $null).ToString('yyyy-MM-dd')
}
New-Object psobject -Property $Properties
} | Export-Excel $outputcsv
Just updating because I may not have been clear on what the issue is. In the columns 'DueDate', 'PeriodFrom', 'PeriodTo', and 'CreatedDate', these are all pulled from the REST API. 'DueDate' and 'CreatedDate' seem to be properly populated but 'PeriodFrom' and 'PeriodTo' may or may not have a date.
Currently, without any trickery, the date (if it exists) returns as:
2019-09-05T00:00:00
I would like to return an empty field if it does not exist or return the date in the like the following if it does exit:
2019-09-05
You can use TryParseExact:
ForEach-Object {
$parsedDate = [datetime]::MinValue
$Properties = [ordered]#{ # remove the CreatedDate from the HashTable
[...]
if ([datetime]::TryParseExact($_.CreatedDate,'yyyy-MM-ddThh:mm:ss',
[System.Globalization.CultureInfo]::InvariantCulture,
[System.Globalization.DateTimeStyles]::None,[ref]$ParsedDate))
{$Properties.Add("CreatedDate", $parsedDate.ToString("yyyy-MM-dd"))}
}
else {
$Properties.Add("CreatedDate",$null)
}
Note that you can add array of string formats instead of a single string, for example:
[String[]]$DateStrings = "M/dd/yyyy h:mmtt","M/d/yyyy h:mmtt","MM/d/yyyy h:mmtt"
Then it would be:
[datetime]::TryParseExact($_.CreatedDate,$DateStrings...)

How to get creation date in sitecore with powershell

I wrote a script in order to replace the "$date" in release date of many Sitecore items with their creation date (created).
I have a problem to get this field from Sitecore.
I tried this:
$rootItem = Get-Item master:/content
$sourceTemplate = Get-Item "/sitecore/content/.../item 1"
foreach($field in $sourceTemplate.Fields) {
if (($field -ne $null) -And ($field -like '$date')) {
$sourceTemplate.Editing.BeginEdit()
$CreatedDate = .......
$field.Value = [sitecore.dateutil]::ToIsoDate($CreatedDate)
$sourceTemplate.Editing.EndEdit()
}
}
I also tried to get this field by ID but it doesn't work.
Does someone have an idea please?
Thank you
If you want to check Sitecore built-in fields, you need to call $sourceTemplate.Fields.ReadAll(); first.
You should compare value of the field with $date string, not the field itself.
Then just get the string which is stored in the __Created field instead of getting date and then formatting it back to ISO date string.
And the last thing - don't call Editing.BeginEdit() and Editing.EndEdit() mutliple times for the same item - Sitecore runs some havily operations when it's called so make sure you only call it once per every item which needs it.
$sourceTemplate = Get-Item "/sitecore/content/home/test"
$sourceTemplate.Fields.ReadAll();
$editing = $false
foreach($field in $sourceTemplate.Fields) {
if ($field.Value -eq '$date') {
if (!$editing) {
$editing = $true
$sourceTemplate.Editing.BeginEdit();
}
$field.Value = $sourceTemplate.Fields["__Created"].Value
}
}
if ($editing) {
$edited = $sourceTemplate.Editing.EndEdit();
}

PowerShell Get-ChildItem with variable and string

Back at it again trying to delete files using PowerShell.
What I am attempting now is deleting files that begin with "Export_" and then a two-digit year followed by the Julian date. For example, today would be Export_18309. There are additional numbers after 18309, but they are not relevant. With my current code "Export_" is a string and my date has been converted to an Int so that I can do math and increment it.
I set $dval to 18302 as a test case for files I was working with. I know it isn't currently deleting anything as I am outputting to a text file for manual verification before continuing. However, if the output of the text file isn't correct then there is no sense attempting the deletion.
My issue currenly is within the function WriteArch block of code. I am trying to combine a string and int to get PowerShell to display results that only begin with "Export_" and $dval as Export_$dval. I made that specific line of code the way it is to better portray what I am attempting.
Edit: The for loop within WriteArch is to increment 18302, to 18303, then 18304, etc as I am deleting files for the week prior to when the script is ran.
$path0 = 'U:\PShell\Testing\IBM3 Cleanup\logfiles'
$archLog = 'U:\PShell\Testing\IBM3 Cleanup\LogArchive.txt'
$day = (Get-Date).DayOfWeek
$date = (Get-Date).ToString("yy") + ((Get-
Date).DayOfYear).ToString("D3")
$dval = $date -as [Int]
$dval = Switch ($day) {
"Monday" { ($dval - 7); break }
"Tuesday" { ($dval - 8); break }
"Wednesday" { ($dval - 9); break }
"Thursday" { ($dval - 10); break }
"Friday" { ($dval - 11) }
}
Clear-Content $archLog
$dval = 18302
function WriteArch {
param( $dval, $path0)
for ($i = 0; $i -le 4; $i++) {
Get-ChildItem -Path $path0|
Where { $_.FullName -Match "Export_" + "$dval"}|
Add-Content $archLog
$dval++
}
}
WriteArch $dval $path0
Invoke-Item $archLog
The Match operator supports regex. I think that's a bit overkill for the scenario you describe - I suggest using Like. The * is essential as it tells PowerShell there's more to the name after what you have specified.
Fullname contains the full path, including parent directories etc - it sounds like you want to base the match on BaseName, which the the file name excluding extension.
Where { $_.BaseName -like "Export_$dval*"}
Putting the $dval variable in quotes forces PowerShell to implicitly convert the int to string.
Comparison operators documentation, a good reference.

Powershell - Array Range from user input

What would be the easiest way to get user input for an array range.
For example:
function MyArrayOfMachines {
[CmdletBinding()]
param(
[parameter(Mandatory=$true)][string]$Machine
# What should I assign the $Range variable as?
)
# Hardcoded range. User should be able to enter the range
$Range = 2..5
for ($i=0; $i -lt $array.length; $i +=1)
{
$result = $array[$i]
$output = $machine+$result
$output
}
}
The above function should take the input as the name of the machine and the array range. For now I have the array range hardcoded. When I assign $Range as [Array]$Range in the user prompt, there is a prompt for $Range[0] etc etc. But I would like the user the enter the range.
Doesn't this work? Unless I misunderstood your question...
function test($range){
$range
}
test -range (1..5)
You can also accept the range as a string and parse it yourself:
function Test
{
param($range)
if($range -is [String])
{
[int]$start, [int]$end = $range.split('.', [StringSplitOptions]::RemoveEmptyEntries)
$start..$end
}
else
{
$range
}
}
The reason for the if / else is for cases where the user passes an actual range, as in manojlds answer, rather than a string to be parsed (like 1..5). This means you can't strongly type the param though.
Make it two parameters:
function test{
param ( [int]$st,
[int]$end)
$Range = $st..$end
$Range
}
test 1 5
If they input the start and end of the range you can use that to create it dynamically in the function.
EDIT:
To get the range from a string, try:
function test{
param ($Range)
$NewRange = $Range.substring(0,($Range.indexof('.')))..$Range.substring(($Range.lastindexof('.') + 1))
$NewRange
}
test 1..5
I agree with #manojlds, the range should be passed in as an array. Parsing a string limits the possibilities of what a user could enter. By using [int[]] you can force the user to specify an array of integers. This would also allow a user to specify a broken range such as ((2..4)+(6..12)) which is harder to allow for when parsing strings.
In your example I'm not sure where $array is coming from, and you only need one line to return a computed machine name.
function MyArrayOfMachines {
param(
[parameter(mandatory=$true)]
[string] $machine,
[parameter(mandatory=$true)]
[int[]] $range
)
foreach($n in $range) {
$machine+$n
}
}
You could create a single machine name,
MyArrayOfMachines Laptop 1
a range of machines,
MyArrayOfMachines Workstation (2..10)
or a non-consecutive array of machines
MyArrayOfMachines Server ((2..3)+(5..9))
You could just pass a string and evaluate it:
function Test([string]$range) {
if ($Range -match '^\d+\.\.\d+$') {
$RangeArray = Invoke-Expression $Range
} else {
$RangeArray = 1..5
}
}
Some minimal validation is done to ensure that the user cannot pass arbitrary code.