How to address mutable parameters like datatime in Powershell?

43 views Asked by At

We have a server that is running out of space (435Gb remaining) and we want to know when it will as new 1Gb files are created every 15 hours consistently. So, I've crafted a script where users can enter a start date, and units of 15 hours. The script will then display the date and time after the two parameters are provided. Challenge I am having is that Powershell returns and out of range error. Is AddHours somehow violating the mutability of the date provided? Not sure how I can get around that.

# enter start date
$startDate = Read-Host "Enter the start date (MM/dd/yyyy)"

# enter in increments of 15 hours
$increments = Read-Host "Enter the number of 15-hour increments"

# convert the start date
$startDateObj = [DateTime]::ParseExact($startDate, "MM/dd/yyyy", $null)

# calculate future date applying 15 hr increments
$endDateObj = $startDateObj.AddHours($increments * 15)

# display the end date and time
Write-Host "End Date and Time: $($endDateObj.ToString("MM/dd/yyyy hh:mm tt"))"

This is the error generated

Exception calling "AddHours" with "1" argument(s): "Value to add was out of range.
Parameter name: value"
At line:11 char:1
+ $endDateObj = $startDateObj.AddHours($increments * 15)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentOutOfRangeException
 
You cannot call a method on a null-valued expression.
At line:14 char:34
+ ... t "End Date and Time: $($endDateObj.ToString("MM/dd/yyyy hh:mm tt"))"
+                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : InvokeMethodOnNull
2

There are 2 answers

1
sirtao On BEST ANSWER

As other already said, the issue is in the comparison.
Best Practice wills putting the known scalar value always on the left of the comparison.

That said, you might also just force-cast the value from Read-Host with [int](Read-Host)

And given you are giving this script to end-users, have some input-validation

# enter start date
$DateFormat = 'dd/MM/yyyy'
# initializing necessary for TryParseExact
$StartDate = [datetime]::Now

Write-Host 'Enter the start date (MM/dd/yyyy)'
while (
    # parse the date typed, returns a Boolean but also loads the reasulting [Datetime] object to $StartDate if successful
    $false -eq [datetime]::TryParseExact((Read-Host), $DateFormat, [cultureinfo]::InvariantCulture, [System.Globalization.DateTimeStyles]::None, [ref]$startDate)
) {
    Write-Host 'Date was not valid format "MM/dd/yyy"' -ForegroundColor Yellow
    Write-Host 'Insert again or press CTRL+C to exit'
}

Write-Host "Start Date: $($startdate.ToLongDateString())  $($startdate.ToLongTimeString())"


# enter in increments of 15 hours
# use the values you feel useful for $Min and $Max
$MinIncrement = [int]1
$MaxIncrement = [int]100
Write-Host "Enter the number of 15-hour increments (between $MinIncrement and $MaxIncrement)"

while (($IncrementValue = [int](Read-Host)) -notin $MinIncrement..$MaxIncrement ) {
    Write-Host "Value $IncrementValue outside range of $Minincrement - $MaxIncrement" -ForegroundColor Yellow
    Write-Host 'Insert again or press CTRL+C to exit'
}
# Note: with ranges greater than 256 using `-in\-notin` becomes progressively less efficient FAST
# in that scenario, though seems unlikely in your use-case, use the following commented version instead
<#

$IncrementValue = [int](Read-Host)
until (($IncrementValue -ge $MinIncrement) -and ($IncrementValue -le $MaxIncrement) ) {
    Write-Host "Value $IncrementValue outside range of $Minincrement - $MaxIncrement" -ForegroundColor Yellow
    Write-Host 'Insert again or press CTRL+C to exit'
    $IncrementValue = [int](Read-Host)
}

#>


# calculate future date applying 15 hr increments
$IncrementHours = 15 * $IncrementValue
Write-Host "Hours to the end of space: $IncrementHours"

# display the end date and time
$endDate = $startDate.AddHours($IncrementHours)
Write-Host "End Date and Time: $($endDate.ToString('MM/dd/yyyy hh:mm tt'))"
0
Santiago Squarzon On

The issue is that you have a string from Read-Host in the left-hand side of your multiplication and in PowerShell, the Multiplication (*) operator will create a copy of the string in the left-hand side determined by the count in the right-hand side:

"2" * 10 # 2222222222

And the solution is as simple as putting the integer first so that it coerces the string on the right-hand side to an integer and performs the normal arithmetic operation:

10 * "2" # 20

Alternatively, you can convert your string to an integer first:

[int] "2" * 10 # 20