Search code examples
powershellfunctiondateoutput

Getting a specific value as an output from a function


I have written a function, which is getting a start date and how many days it have to add to it. Next it is checking how many of them are working days. And in general it works, it is calculating what it should. The problem is the output. It should be the number of working days. But I get two information: the last date which was checked and the number of days. Could you please tell me why is it happening and how to get only the number of days? Thank you!

Here is the function (maybe it could be written easier, but I'm new to PowerShell, I also used some solutions I found here)

Function Get-WorkingDay{
    param(
        [Parameter(Mandatory=$True,Position=1)]
        [datetime]$startDate,
        [Parameter(Mandatory=$True,Position=2)]
        [int]$Duration    
        )

        Write-Host $startDate
        Write-Host $Duration


    $holidays = @(
        (Get-Date -Date '2014-01-01'),            # New_Years_Day2014
        (Get-Date -Date '2014-01-20'),            # Martin_Luther_King2014
        (Get-Date -Date '2014-02-17'),            # Washingtons_Birthday2014
        (Get-Date -Date '2014-04-18'),            # Good_Friday2014 
        (Get-Date -Date '2014-05-26'),            # Memorial_Day2014 
        (Get-Date -Date '2014-07-04'),            # Independence_Day2014
        (Get-Date -Date '2014-09-01'),            # Labor_Day2014
        (Get-Date -Date '2014-11-27'),            # Thanksgiving_Day2014
        (Get-Date -Date '2014-12-25'),            # Christmas2014
        (Get-Date -Date '2015-01-01'),            # New_Years_Day2015
        (Get-Date -Date '2015-01-19'),            # Martin_Luther_King2015
        (Get-Date -Date '2015-02-16'),            # Washingtons_Birthday2015 
        (Get-Date -Date '2015-04-03'),            # Good_Friday2015 
        (Get-Date -Date '2015-05-25'),            # Memorial_Day2015 
        (Get-Date -Date '2015-07-03'),            # Independence_Day2015
        (Get-Date -Date '2015-09-07'),            # Labor_Day2015 
        (Get-Date -Date '2015-11-26'),            # Thanksgiving_Day2015
        (Get-Date -Date '2015-12-25')             # Christmas2015
        )

        $dateIndex = $startDate.AddDays(1)
        [Int]$WorkingDays = 0
        Write-Host "working days before counting" $WorkingDays
        

        For($DayIndex = 1; $DayIndex -le $Duration; $DayIndex++){
            
            Write-Host "getting into for loop"
            Write-Host "day nr" $DayIndex
            Write-Host "date" $dateIndex
            Write-Host "duration" $Duration
        
           Do{
                If (("Sunday","Saturday" -contains $dateIndex.DayOfWeek) -or ($holidays -contains $dateIndex)){
                    # This is not a working day. Check the next day.
                    Write-Host "$($dateIndex.Date) is a $($dateIndex.DayOfWeek) and is weekend or holiday and it's a day no $DayIndex, working day no $WorkingDays"
                    
                    If (("Saturday" -contains $dateIndex.DayOfWeek) -or ($holidays -contains $dateIndex.AddDays(1))){
                        $DayIndex += 1
                        }
                    
                    $dateIndex = $dateIndex.AddDays(1)
                    $isWorkingDay = $False
                } Else {
                    # Current $dateIndex is a working day.
                    
                    If (!$isWorkingDay) {
                        $DayIndex += 1
                        }

                    $WorkingDays += 1
                    $isWorkingDay = $True
                    Write-Host "$($dateIndex.Date) is a $($dateIndex.DayOfWeek) and is a working day and it's a day no $DayIndex, working day no $WorkingDays"
                    Write-Host "Working days" $WorkingDays
                }
            } 
            
            While(!$isWorkingDay)
            # Set the $dateIndex to the next day.
            $dateIndex = $dateIndex.AddDays(1)    
        }

        # The last date was the end one. Minus the day. 
        $dateIndex.AddDays(-1)

        Write-Host "Final working days $WorkingDays"
        Write-Output $WorkingDays
}

And here is what I get (assuming that the start date for function was 2014.01.21). I need to get just 7.

2014.01.31 00:00:00 7

Solution

  • As commented, your script "leaks" to the pipeline.
    It is important to understand here that PowerShell doesn't explicitly require the Write-Output command to put something on the pipeline, and:

    The default behavior is to display the output at the end of a pipeline. In PowerShell, it is generally not necessary to use the cmdlet in instances where the output is displayed by default.

    Yet it is sometimes difficult to determine where a function leaks to the pipeline. Hence my (rejected) propose for #15781 Strict Write-Output. The splendid suggestion from mklement0 in the propose is a very good alternative to reveal this. I have modified it a bit and created an advanced function for this:

    function Show-Output {
        [CmdletBinding()]
        Param(
            [Parameter(ValueFromPipeLine = $True, Mandatory = $True)][string]$Path
        )
        Process {
            $Caller = (Get-PsCallStack)[1]
            Write-Verbose -Verbose "Output from $($Caller.Location) in $($Caller.functionName) at line $($Caller.ScriptLineNumber): $($Caller.Position.Text)"
            $_
        }
    }
    

    Get-WorkingDay 2014.01.21 1 | Show-Output
    

    enter image description here

    In other words, the solution to your issue is to purge the output from the $dateIndex.AddDays(-1) method. There are several ways to do this, along with assigning it to a $Null variable:

    $Null = $dateIndex.AddDays(-1)
    

    See also this helpful answer from mklement0.