powershellscriptingpowershell-2.0immutabilitypowershell-3.0

How to address mutable parameters like datatime in Powershell?


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

Solution

  • 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'))"