CalendarHoliday.psm1

function New-CalendarHoliday {
    <#
        .SYNOPSIS
            Add a Holiday apointment to users default calendar.
 
        .DESCRIPTION
            Add a Holiday apointment to users default calendar.
            takes Holiday information from CSV or manual input and uses EWS API to create an apointment with ApplicationImpersonation rights in users defaul calendar.
 
        .NOTES
            Author: Michael Moshkovich
            Last Edit: 2020/08/25
            Version 1.0 - Initial release of New-CalendarHoliday
 
            Inspiered by Andrei Ghita's post: https://developermessaging.azurewebsites.net/2012/10/29/outlook-code-importing-bank-holidays-from-an-outlook-hol-based-csv-file/
 
        .PARAMETER FilePath
            Specifies the path to CSV file.
 
            File must be in the following format:
 
            Country,Holiday,Date,FreeBusyStatus
            USA,Test US Holiday - "Free",2020/08/26,Free
            Brazil,Test Brazil Holiday - "OOF",2020/12/23,OOF
            Canada,Test Canada Holiday - "Busy",2020/01/01,Busy
 
        .PARAMETER FilterCountry
            Specify the Country of which to create Holidays entries.
 
        .PARAMETER Country
            Specify Country for manual input.
 
        .PARAMETER Holiday
            Specify Holiday for manual input.
 
        .PARAMETER Date
            Specify Date for manual input.
            Accepted Format: yyyy/MM/dd
 
        .PARAMETER FreeBusyStatus
            Specify FreeBusyStatus for manual input.
            Accepted values:
                * Free
                * Tentative
                * Busy
                * OOF
                * WorkingElsewhere
                * NoData
 
        .PARAMETER PrimarySmtpAddress
            Specify PrimarySmtpAddress to impersonate.
 
        .PARAMETER EwsUrl
            Specify URL to Exchange EWS WebServicesVirtualDirectory.
            will use Office 365 by Default ("https://outlook.office365.com/ews/exchange.asmx").
         
        .PARAMETER Credentials
            Specify Impersonating user credentials.
            if not specified, will try to use DefaultCredentials.
 
        .INPUTS
            PSobject. You can pipe objects to New-CalendatHoliday.
            Only $_.PrimarySmtpAddress will be mapped to PrimarySmtpAddress Parameter.
 
        .OUTPUTS
            None. New-CalendatHoliday does not returns any output.
 
        .EXAMPLE
            C:\PS> New-CalendarHoliday -Country "Canada" -Holiday "Test Canada Holiday - "Busy"" -Date 2020/01/01 -FreeBusyStatus Busy -PrimaryEmailAddress john.d@contoso.com -Credentials $Cred
            This Example will add manualy specified Holiday in Canada to "john.d@contoso.com" using specified Credentials.
         
        .EXAMPLE
            C:\PS> New-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -PrimaryEmailAddress john.d@contoso.com
            This Example will add all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" to "john.d@contoso.com" using Default Credentials.
 
        .EXAMPLE
            C:\PS> Get-Mailbox -Filter "RecipientType -eq 'UserMailbox'" | New-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -Credentials $Cred
            This Example will add all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" to all mailboxes of Type "UserMailbox" using specified Credentials.
 
        .EXAMPLE
            C:\PS> Get-User -Filter "CountryOrRegion -eq 'Canada'" | Get-Mailbox -Filter "RecipientType -eq 'UserMailbox'" | New-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -Credentials $Cred
            This Example will add all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" to all mailboxes of Type "UserMailbox" of users with CountryOrRegion set to "Canada" using specified Credentials.
 
        .LINK
            https://u-btech.com/support
 
        .LINK
            Remove-CalendarHoliday
 
        .LINK
            Inspiered by Andrei Ghita's post: https://developermessaging.azurewebsites.net/2012/10/29/outlook-code-importing-bank-holidays-from-an-outlook-hol-based-csv-file/
    #>

    [CmdletBinding()]
    param(
        [Parameter(ParameterSetName="FileSet", Mandatory=$true)]
        [string]$FilePath,

        [Parameter(ParameterSetName="FileSet", Mandatory=$true)]
        [String]$FilterCountry,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [string]$Country,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [string]$Holiday,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [ValidatePattern("^(\d{4})[\.\-\/](0?[1-9]|1[012])[\.\-\/](0?[1-9]|[12][0-9]|3[01])$")]
        [string]$Date,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [ValidateSet("Free", "Tentative", "Busy", "OOF", "WorkingElsewhere", "NoData")]
        [string]$FreeBusyStatus,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias("SmtpAddress", "EmailAddress")]
        [string]$PrimarySmtpAddress,

        [Parameter(Mandatory=$false)]
        [string]$EwsUrl = "https://outlook.office365.com/ews/exchange.asmx",

        [Parameter(Mandatory=$false)]
        [PSCredential]$Credentials
    )
    begin {
        # $Service.Credentials = New-Object Net.NetworkCredential('UserName', 'Password', "zone")
        $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)
        if ($Credentials) {
            $Service.Credentials = New-Object Net.NetworkCredential($Credentials.UserName, $Credentials.Password)
        }
        if($PsCmdlet.ParameterSetName -eq "FileSet") {
            $holidayList = Import-Csv -Path $FilePath
        }
        if($PsCmdlet.ParameterSetName -eq "ManualSet") {
            $holidayList = New-Object PSObject -Property @{
                "Country" = $country
                "Holiday" = $holiday
                "Date" = $date
                "FreeBusyStatus" = $freeBusyStatus
            }
            $FilterCountry = $holidayList.Country
        }
        
        $Service.Url = New-Object -TypeName System.Uri $EwsUrl

    }
    process{
        Write-Verbose "Accessing Mailbox $PrimarySmtpAddress"
        Write-Verbose "Importing Holidays for $FilterCountry"

        $Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $PrimarySmtpAddress);
        # $Service.AutodiscoverUrl($EmailAddress, {$True})
 
        # This is the root folder from where we want to start searching for the folder we want to delete.
        # Once we find it, then we will go recursively down that folder.
        # Other option would be to get the FolderID and start the search straight from there

        $CalendarFolderId = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $PrimarySmtpAddress)
        #$RootFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root, $EmailAddress)
        $CalendarFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $CalendarFolderId)
        foreach ($item in $holidayList) {
            if ($item.Country -eq $FilterCountry) {
                Write-Verbose "Holiday name and date: `"$($item.Holiday)`", $($item.Date)"
                $SearchFilterCollection = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
                $SearchFilter1 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Subject,$item.Holiday)
                $Start = New-Object System.DateTime
                $Start = [System.DateTime]::Parse($($item.Date))
                $SearchFilter2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Start, $Start)
                $SearchFilter3 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Location,$item.Country)
                $SearchFilterCollection.Add($SearchFilter1)
                $SearchFilterCollection.Add($SearchFilter2)
                $SearchFilterCollection.Add($SearchFilter3)


                $itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(20)
                $itemView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
         
                $findResults = $Service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$SearchFilterCollection,$itemView)
        
                if ($findResults.Items.Count -eq 0) {
            
                    # Create Object
                    $Appointment = New-Object Microsoft.Exchange.WebServices.Data.Appointment -ArgumentList $service   
             
                    # Set Subject
                    $Appointment.Subject = $item.Holiday

                    # Set Category
                    #$Appointment.Categories = new-object Collections.Generic.List[string]
                    $Appointment.Categories.Add("Holiday")

                    # Set Location
                    $Appointment.Location = $item.Country

                    # Set Start Time
                    $Appointment.Start = [System.DateTime]::Parse($item.Date)
            
                    # Set End Time
                    $Appointment.End = [System.DateTime]::Parse($item.Date).AddDays(1)

                    # Mark as all day event
                    $Appointment.IsAllDayEvent = $true;

                    # Show as free
                    $Appointment.LegacyFreeBusyStatus = [Microsoft.Exchange.WebServices.Data.LegacyFreeBusyStatus]::$($item.FreeBusyStatus)
                    #Create Appointment will save to the default Calendar

                    # Set no Reminder
                    $Appointment.IsReminderSet = $false

                    # Save Appointmant
                    $Appointment.Save([Microsoft.Exchange.WebServices.Data.SendInvitationsMode]::SendToNone)  

                    Write-Verbose "Holiday created."
                }else{
                    Write-Verbose "Holiday exists."
                }
            }
        
        }
     
        $findResults = $null
        $itemView = $null
        $SearchFilter1 = $null
        $SearchFilter2 = $null
        $SearchFilderCollection = $null
        $CalendarFolder = $null
        $CalendarFolderId = $null
        $Service.ImpersonatedUserId = $null

    }
    end{}
}

function Remove-CalendarHoliday {
        <#
        .SYNOPSIS
            Removes a Holiday apointment from users default calendar.
 
        .DESCRIPTION
            Removes a Holiday apointment from users default calendar.
            takes Holiday information from CSV or manual input and uses EWS API to remove an apointment with ApplicationImpersonation rights from users defaul calendar.
 
        .NOTES
            Author: Michael Moshkovich
            Last Edit: 2020/08/25
            Version 1.0 - Initial release of Remove-CalendarHoliday
 
            Inspiered by Andrei Ghita's post: https://developermessaging.azurewebsites.net/2012/10/29/outlook-code-importing-bank-holidays-from-an-outlook-hol-based-csv-file/
 
        .PARAMETER FilePath
            Specifies the path to CSV file.
 
            File must be in the following format:
 
            Country,Holiday,Date,FreeBusyStatus
            USA,Test US Holiday - "Free",2020/08/26,Free
            Brazil,Test Brazil Holiday - "OOF",2020/12/23,OOF
            Canada,Test Canada Holiday - "Busy",2020/01/01,Busy
 
        .PARAMETER FilterCountry
            Specify the Country of which to remove Holidays entries.
 
        .PARAMETER Country
            Specify Country for manual input.
 
        .PARAMETER Holiday
            Specify Holiday for manual input.
 
        .PARAMETER Date
            Specify Date for manual input.
            Accepted Format: yyyy/MM/dd
 
        .PARAMETER FreeBusyStatus
            Specify FreeBusyStatus for manual input.
            Accepted values:
                * Free
                * Tentative
                * Busy
                * OOF
                * WorkingElsewhere
                * NoData
 
        .PARAMETER PrimarySmtpAddress
            Specify PrimarySmtpAddress to impersonate.
 
        .PARAMETER EwsUrl
            Specify URL to Exchange EWS WebServicesVirtualDirectory.
            will use Office 365 by Default ("https://outlook.office365.com/ews/exchange.asmx").
         
        .PARAMETER Credentials
            Specify Impersonating user credentials.
            if not specified, will try to use DefaultCredentials.
 
        .INPUTS
            PSobject. You can pipe objects to Remove-CalendatHoliday.
            Only $_.PrimarySmtpAddress will be mapped to PrimarySmtpAddress Parameter.
 
        .OUTPUTS
            None. Remove-CalendatHoliday does not returns any output.
 
        .EXAMPLE
            C:\PS> Remove-CalendarHoliday -Country "Canada" -Holiday "Test Canada Holiday - "Busy"" -Date 2020/01/01 -FreeBusyStatus Busy -PrimaryEmailAddress john.d@contoso.com -Credentials $Cred
            This Example will remove manualy specified Holiday in Canada from "john.d@contoso.com" using specified Credentials.
         
        .EXAMPLE
            C:\PS> Remove-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -PrimaryEmailAddress john.d@contoso.com
            This Example will remove all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" from "john.d@contoso.com" using Default Credentials.
 
        .EXAMPLE
            C:\PS> Get-Mailbox -Filter "RecipientType -eq 'UserMailbox'" | Remove-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -Credentials $Cred
            This Example will remove all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" from all mailboxes of Type "UserMailbox" using specified Credentials.
 
        .EXAMPLE
            C:\PS> Get-User -Filter "CountryOrRegion -eq 'Canada'" | Get-Mailbox -Filter "RecipientType -eq 'UserMailbox'" | Remove-CalendarHoliday -FilePath "C:\Users\user\Desktop\holidays_template.csv" -FilterCountry "Canada" -Credentials $Cred
            This Example will remove all Canada Holidays specified in CSV file "C:\Users\user\Desktop\holidays_template.csv" from all mailboxes of Type "UserMailbox" of users with CountryOrRegion set to "Canada" using specified Credentials.
 
        .LINK
            https://u-btech.com/support
 
        .LINK
            New-CalendarHoliday
 
        .LINK
            Inspiered by Andrei Ghita's post: https://developermessaging.azurewebsites.net/2012/10/29/outlook-code-importing-bank-holidays-from-an-outlook-hol-based-csv-file/
    #>

    [CmdletBinding()]
    param(
        [Parameter(ParameterSetName="FileSet", Mandatory=$true)]
        [string]$FilePath,

        [Parameter(ParameterSetName="FileSet", Mandatory=$true)]
        [String]$FilterCountry,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [string]$Country,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [string]$Holiday,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [ValidatePattern("^(\d{4})[\.\-\/](0?[1-9]|1[012])[\.\-\/](0?[1-9]|[12][0-9]|3[01])$")]
        [string]$Date,

        [Parameter(ParameterSetName="ManualSet", Mandatory=$true)]
        [ValidateSet("Free", "Tentative", "Busy", "OOF", "WorkingElsewhere", "NoData")]
        [string]$FreeBusyStatus,

        [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true)]
        [Alias("SmtpAddress", "EmailAddress")]
        [string]$PrimarySmtpAddress,

        [Parameter(Mandatory=$false)]
        [string]$EwsUrl = "https://outlook.office365.com/ews/exchange.asmx",
        
        [Parameter(Mandatory=$false)]
        [PSCredential]$Credentials
    )
    begin {
        $Service = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::Exchange2013_SP1)
        # $Service.Credentials = New-Object Net.NetworkCredential('UserName', 'Password', "zone")
        if ($Credentials) {
            $Service.Credentials = New-Object Net.NetworkCredential($Credentials.UserName, $Credentials.Password)
        }
        $cred = $Credentials
        if($PsCmdlet.ParameterSetName -eq "FileSet") {
            $holidayList = Import-Csv -Path $FilePath
        }
        if($PsCmdlet.ParameterSetName -eq "ManualSet") {
            $holidayList = New-Object PSObject -Property @{
                "Country" = $country
                "Holiday" = $holiday
                "Date" = $date
                "FreeBusyStatus" = $freeBusyStatus
            }
            $FilterCountry = $holidayList.Country
        }

        $Service.Url = New-Object -TypeName System.Uri $EwsUrl
        
    }
    process{
        Write-Verbose "Accessing Mailbox $PrimarySmtpAddress"
        Write-Verbose "Importing Holidays for $FilterCountry"

        $Service.ImpersonatedUserId = New-Object Microsoft.Exchange.WebServices.Data.ImpersonatedUserId([Microsoft.Exchange.WebServices.Data.ConnectingIdType]::SmtpAddress, $PrimarySmtpAddress);
        # $Service.AutodiscoverUrl($EmailAddress, {$True})
 
        # This is the root folder from where we want to start searching for the folder we want to delete.
        # Once we find it, then we will go recursively down that folder.
        # Other option would be to get the FolderID and start the search straight from there

        $CalendarFolderId = New-Object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar, $PrimarySmtpAddress)
        #$RootFolderID = new-object Microsoft.Exchange.WebServices.Data.FolderId([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Root, $EmailAddress)
        $CalendarFolder = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($Service, $CalendarFolderId)
        foreach ($item in $holidayList) {
            if ($item.Country -eq $FilterCountry) {
                Write-Verbose "Holiday name and date: `"$($item.Holiday)`", $($item.Date)"
                $SearchFilterCollection = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+SearchFilterCollection([Microsoft.Exchange.WebServices.Data.LogicalOperator]::And)
                $SearchFilter1 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Subject,$item.Holiday)
                $Start = New-Object System.DateTime
                $Start = [System.DateTime]::Parse($($item.Date))
                $SearchFilter2 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Start, $Start)
                $SearchFilter3 = New-Object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.AppointmentSchema]::Location,$item.Country)
                $SearchFilterCollection.Add($SearchFilter1)
                $SearchFilterCollection.Add($SearchFilter2)
                $SearchFilterCollection.Add($SearchFilter3)

                $itemView = New-Object Microsoft.Exchange.WebServices.Data.ItemView(20)
                $itemView.PropertySet = New-Object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
         
                $findResults = $Service.FindItems([Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Calendar,$SearchFilterCollection,$itemView)
        
                if ($findResults.Items.Count -gt 0) {
                    Write-Verbose "Holiday exists."
                    foreach($findResult in $findResults.Items) { 
                        $findResult.Load()
                        $findResult.Delete([Microsoft.Exchange.WebServices.Data.DeleteMode]::HardDelete)
                        Write-Verbose "Holiday deleted."
                    }
                }
            }
        
        }
     
        $findResults = $null
        $itemView = $null
        $SearchFilter1 = $null
        $SearchFilter2 = $null
        $SearchFilderCollection = $null
        $CalendarFolder = $null
        $CalendarFolderId = $null
        $Service.ImpersonatedUserId = $null

    }
    end{}
}

Export-ModuleMember -Function New-CalendarHoliday, Remove-CalendarHoliday