Search code examples
powershellactive-directory

Need an updated method for New-Timespan cmdlet in powershell


We are using a powershell script cobbled together from other entries here / spiceworks. After moving this to a newer server, theres some difference somewhere we cannot find and the get timespan line is resulting in the following error:

New-TimeSpan : Cannot bind parameter 'End'. Cannot convert the "60.00:00:00" value of type "System.TimeSpan" to type "System.DateTime".
At C:\ExpirationScript\PasswordChangeNotification.ps1:61 char:54
+     $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
+                                                      ~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [New-TimeSpan], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgumentNoMessage,Microsoft.PowerShell.Commands.NewTimeSpanCommand

Tried a few different ideas found on stackoverflow to replace this variable without success:

$daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days`

Any ideas? Thank you in advance.

Script being used:

# Please Configure the following variables....
$smtpServer="localhost"
$expireindays = 7
$from = "[email protected]"
$logging = "Enabled" # Set to Disabled to Disable Logging
$logFile = "c:\pathtoscripting\ADpasscheck.csv" # ie. c:\mylog.csv
$testing = "Disabled" # Set to Disabled to Email Users
$testRecipient = "[email protected]"
$nullRecipient = "[email protected]"
$date = Get-Date -format ddMMyyyy
$exemptUsers = "Administrator"
#
############################################################################

# Check Logging Settings
if (($logging) -eq "Enabled")
{
    # Test Log File Path
    $logfilePath = (Test-Path $logFile)
    if (($logFilePath) -ne "True")
    {
        # Create CSV File and Headers
        New-Item $logfile -ItemType File
        Add-Content $logfile "Date,Name,EmailAddress,DaystoExpire,ExpiresOn"
    }
} # End Logging Check

# Get Users From AD who are Enabled, Passwords Expire and are Not Currently Expired #Enabled was missing from this line so disabled accounts were being found
Import-Module ActiveDirectory
$users = get-aduser -filter 'Enabled -eq $true' -properties Name, PasswordNeverExpires, PasswordExpired, PasswordLastSet, EmailAddress
$maxPasswordAge = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge

# Process Each User for Password Expiry
foreach ($user in $users)
{
    If ($exemptUsers -contains $user.SamAccountName) { Continue; }
    $Name = (Get-ADUser $user | foreach { $_.Name})
    $emailaddress = $user.emailaddress
    $passwordSetDate = (get-aduser $user -properties * | foreach { $_.PasswordLastSet })
    $PasswordPol = (Get-AduserResultantPasswordPolicy $user)
    # Check for Fine Grained Password
    if (($PasswordPol) -ne $null)
    {
        $maxPasswordAge = ($PasswordPol).MaxPasswordAge
    }
 
    $expireson = $passwordsetdate + $maxPasswordAge
    $today = (get-date)
    $daystoexpire = (New-TimeSpan -Start $today -End $Expireson).Days
       
    # Set Greeting based on Number of Days to Expiry.

    # Check Number of Days to Expiry
    $messageDays = $daystoexpire

    if (($messageDays) -ge "1")
    {
        $messageDays = "in " + "$daystoexpire" + " days."
    }
    else
    {
        $messageDays = "today."
    }
}

Tried reworking line to use other idea found here (date time object) but could not incorporate properly: New-TimeSpan cmdlet in powershell


Solution

  • You're expecting $expireson to be a [datetime] instance, but it is a [timespan] instance, which is why the call to New-TimeSpan fails (the -Start and -End arguments must be [datetime] instaces, i.e. points in time).

    Given how you set the value of $expireson:

    $expireson = $passwordsetdate + $maxPasswordAge
    

    the implication is that $passwordsetdate is $null - which, in effect, makes the assignment equivalent to $expireson = $maxPasswordAge, i.e. stores the [timespan] value contained in $maxPasswordAge in $expireson.

    Only if $passwordsetdate contained a [datetime] instance would the + operation work as intended, i.e. it would add the [timespan] value to the [datetime] instance to yield a new [datetime] instance.

    Simple examples:

    # OK: Adds the timespan to the date and returns the resulting date, 
    #     i.e. [datetime] '1970/01/02'
    [datetime] '1970/01/01' + [timespan]::FromDays(1)
    
    # !! BROKEN: because $allegedDate is $null, the timespan is used as-is.
    $allegedDate = $null
    $allegedDate + [timespan]::FromDays(1)
    

    Therefore:

    • You must ensure that $passwordsetdate contains the intended [datetime] value.

      • I'm not familiar enough with AD to know under what circumstances the value is $null, but based on this ServerFault post, two factors may be at play:
        • Running with elevation may be necessary.
        • $null may be returned if the user will have to change their password on next login.
    • You can then simply directly subtract your two [datetime] instances from each other - no need for New-TimeSpan:

      $daystoexpire = ($expireson - $today).Days