Outlook-Calendar.ps1

function Get-GraphReminderView   {
    <#
      .Synopsis
        Returns a view of items with reminders set across all a users calendars.
    #>

    [cmdletbinding(DefaultParameterSetName="None")]
    param(
        #UserID as a guid or User Principal name, whose calendar should be fetched If not specified defaults to "me"
         [string]$User,
        #Time zone to rennder event times. By default the time zone of the local machine will me use
        $Timezone = $(tzutil.exe /g),
        #Number of days of calendar to fetch from today
        [int]$Days =30 ,
        #The number of events to fetch. Must be greater than zero, and capped at 1000
        [ValidateRange(1,1000)]
        [int]$Top
    )
    Connect-MSGraph
    $webParams = @{Method = "Get"
                  Headers = $Script:DefaultHeader
    }
    if ($TimeZone) {$webParams.Headers["Prefer"]="Outlook.timezone=""$TimeZone"""}

    If ($User)   {  # get the default calendar for a specific user
            if ($User.ID) {$User=$User.ID}
            $uri = "https://graph.microsoft.com/v1.0/users/$user/reminderView(startDateTime='{0:yyyy-MM-ddTHH:mm:ss}',endDateTime='{1:yyyy-MM-ddTHH:mm:ss}')"
    }
    else {  $uri = "https://graph.microsoft.com/v1.0/me/reminderView(startDateTime='{0:yyyy-MM-ddTHH:mm:ss}',endDateTime='{1:yyyy-MM-ddTHH:mm:ss}')"  }

    $webParams['uri'] =  $uri -f [datetime]::Today, [datetime]::Today.AddDays(30)
    $result = Invoke-RestMethod @webParams
    [string[]]$defaultProperties = @('Subject','When','Reminder')
    $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet -ArgumentList 'DefaultDisplayPropertySet',$defaultProperties
    $psStandardMembers = [System.Management.Automation.PSMemberInfo[]] @($defaultDisplayPropertySet)
    $whensb = { if (  ([datetime]$this.eventStartTime.datetime).AddDays(1) -eq ([datetime]$this.eventEndTime.datetime  )) {
                      ([datetime]$this.eventStartTime.datetime).ToShortDateString()
                }
                else {([datetime]$this.eventStartTime.datetime).ToString("g") + ' to ' +  ([datetime]$this.eventEndTime.datetime  ).ToString("g") + $this.eventEndTime.timezone}
    }
    foreach ($r in $result.value) {
        Add-Member -InputObject $r -MemberType AliasProperty  -Name Subject           -Value eventSubject
        Add-Member -InputObject $r -MemberType AliasProperty  -Name Start             -Value eventStartTime
        Add-Member -InputObject $r -MemberType AliasProperty  -Name End               -Value eventEndTime
        Add-Member -InputObject $r -MemberType ScriptProperty -Name When              -Value $whenSB
        Add-Member -InputObject $r -MemberType ScriptProperty -Name Reminder          -Value {([datetime]$this.reminderFireTime.datetime).ToString("g")}
        Add-Member -InputObject $r -MemberType MemberSet      -Name PSStandardMembers -Value $PSStandardMembers
        Add-Member -InputObject $r -MemberType AliasProperty  -Name Location          -Value eventLocation -PassThru
    }
}

function Get-GraphEvent          {
    <#
      .Synopsis
        Get the events in a calendar
      .Description
        Depending on the parameters the events my come from
           * A specified calendar (retrieved by get-graphGroup or Get-GraphUser)
           * The default calendar for a group, (if only -group is provided)
           * The default calendar for a specific user, if only user is specified
           * The default calendar for the current user if no user, group, or calendar is specified.
           The request can specify the first n events in the calendar, or a number of days into
           the future, or specify the subject line or a custom filter.
      .Example
        >
        >$team = Get-GraphTeam -ByName consultants
        >Get-GraphEvent -Team $team
        Finds the team (group) named "Consultants", and gets events in the team's calendar.
        Note that the because "team" and "group" are used interchangably the parameter is
        named "Group" with an alias of "Team"
      .Example
        >
        >get-graphuser -Calendars | where name -match "holidays" |
             get-graphevent -days 365 -order "start/datetime desc" -select start,end, subject |
                format-table subject, when
        Gets the user's calendars and selects the national holidays one;
        gets the events from this calendar for the next 365 days, sorting them to
        soonest last and selecting only the dates and subject; 'when' is calculated from
        start and end, so it is available to the format table command at the end of the pipeline.
      .Example
        >Get-GraphEvent -user alex@contoso.com -filter "isorganizer eq false"
        Gets events from the specified user's calendar where they are not the organizer;
        this requires access to have been granted access to the calendar by its owener.
      .Example
        >Get-GraphEvent -filter "isorganizer eq false" -OrderBy start/datetime
        This uses the same filter as the previous example but sorts the results at the
        server before they are returned. Note that some fields like 'start' are record types,
        and one of their propperties, as in this case, may need to be specified to perform
        a sort, and the syntax is property/ChildProperty.
      .Example
        >
        >$userTimezone = (Get-GraphUser -MailboxSettings).timezone
        >Get-GraphEvent -Days 150 -TimeZone $userTimezone -Filter "showas eq 'free'"
        The first command gets the current user's preferred time zone, which may not
        match the local computer, and the second requests items for the next 150 days,
        where the time is shown as Free, displaying using that time zone
      .Example
        >Get-graphEvent -filter "start/dateTime ge '2019-04-01T08:00'" | ft
        Gets the events in the signed-in user's default calendar which start after April 1 2019
        format-table will pick up the default display properties (Subject, When, Where and ShowAs)
    #>

    [cmdletbinding(DefaultParameterSetName="None")]
    param(
        #UserID as a guid or User Principal name, whose calendar should be fetched.
        [Parameter( Mandatory=$true, ParameterSetName="User"          ,ValueFromPipelineByPropertyName=$true)]
        [Parameter( Mandatory=$true, ParameterSetName="UserAndSubject",ValueFromPipelineByPropertyName=$true)]
        [Parameter( Mandatory=$true, ParameterSetName="UserAndFilter" ,ValueFromPipelineByPropertyName=$true)]
        [string]$User,

        #A sepecific calendar
        [Parameter( Mandatory=$true, ParameterSetName="Cal",           ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Parameter( Mandatory=$true, ParameterSetName="CalAndSubject", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Parameter( Mandatory=$true, ParameterSetName="CalAndFilter",  ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Parameter( ParameterSetName="User",          ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Parameter( ParameterSetName="UserAndSubject",ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        [Parameter( ParameterSetName="UserAndFilter", ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
        $Calendar,

        #Group ID or a Group object with an ID, whose calendar should be fetched
        [Parameter(Mandatory=$true, ParameterSetName="GroupID"        ,ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="GroupAndSubject",ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="GroupAndFilter" ,ValueFromPipelineByPropertyName=$true)]
        [Alias("Team")]
        $Group,

        #Time zone to rennder event times. By default the time zone of the local machine will me use
        $Timezone = $(tzutil.exe /g),

        #Number of days of calendar to fetch from today
        [int]$Days,

        #The neumber of events to fetch. Must be greater than zero, and capped at 1000
        [ValidateRange(1,1000)]
        [int]$Top,

        #Fields to select
        [ValidateSet('attendees', 'body', 'bodyPreview', 'categories', 'changeKey', 'createdDateTime', 'end', 'hasAttachments',
                     'iCalUId', 'id', 'importance', 'isAllDay', 'isCancelled', 'isOrganizer', 'isReminderOn', 'lastModifiedDateTime',
                     'location', 'locations', 'onlineMeetingUrl', 'organizer', 'originalEndTimeZone', 'originalStart',
                     'originalStartTimeZone', 'recurrence', 'reminderMinutesBeforeStart', 'responseRequested', 'responseStatus',
                    'sensitivity', 'seriesMasterId', 'showAs', 'start', 'subject', 'type', 'webLink' )]
        [string[]]$Select,

        #An order-by clause to sort the events
        [string]$OrderBy,

        #If specified, fetch events where the subject line begins with
        [Parameter(Mandatory=$true, ParameterSetName='CalAndSubject',  ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="UserAndSubject", ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="GroupAndSubject",ValueFromPipelineByPropertyName=$true)]
        [string]$Subject,

        #A custom selection filter
        [Parameter(Mandatory=$true, ParameterSetName="CurrentFilter", ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="CalAndFilter",  ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="UserAndFilter", ValueFromPipelineByPropertyName=$true)]
        [Parameter(Mandatory=$true, ParameterSetName="GroupAndFilter",ValueFromPipelineByPropertyName=$true)]
        [string]$Filter
    )
    Connect-MSGraph
    $webParams = @{Method = "Get"
                  Headers = $Script:DefaultHeader
    }
    if ($TimeZone) {$webParams.Headers["Prefer"]="Outlook.timezone=""$TimeZone"""}

    #region figure out which calendar to get. The API doesn't have v1.0/calendars/id, have to do group/calendar user/calendar or user/calendarS/id
    if     ($Calendar -and $Calendar.CalendarPath) {
        $uri = "https://graph.microsoft.com/v1.0/$($Calendar.CalendarPath)"
    }
    elseif ($User -and $Calendar)   { #get a specific calendar for a specific user
        if ($User.ID)     {$User     = $User.ID}
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $uri = "https://graph.microsoft.com/v1.0/users/$user/calendars/$Calendar"
    }
    elseif ($User)   {  # get the default calendar for a specific user
        if ($User.ID) {$User=$User.ID}
        $uri = "https://graph.microsoft.com/v1.0/users/$user/calendar"  #for the default calendar you can also use users/{id}/events or users//calendarView?param without "Calendar"
    }
    elseif ($Group) {  # get the [only] calendar for a group
        if ($Group.ID) {$Group=$Group.ID}
        $uri = "https://graph.microsoft.com/v1.0/groups/$Group/calendar"   #for the default calendar you can also use groups/{id}/events or groups/calendarView?param without "Calendar"
    }
    elseif ($Calendar -and $Calendar.GroupID ) { #handle piping in a group's calendar object - we have added the group ID to it, us that get the group's [only] calendar
        $uri = "https://graph.microsoft.com/v1.0/groups/$($calendar.groupID)/calendar"
    }
    elseif ($calendar) { #get a specific calendar for the current user - more normal use of the calendar parameter
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $uri = "https://graph.microsoft.com/v1.0/me/calendars/$calendar"
    }
    else  {  #no User, group or calendar specified get the current users default calendar.
        $uri = "https://graph.microsoft.com/v1.0/me/calendar"   #for the default calendar you can also use me/events or me/calendarView?params without "Calendar"
    }
    #endregion
    #region apply the selection criteria. If -days is specified use calendar view, otherwise use events and add filter, orderby, select and top as needed
    if  ($days)    {
                       $start = [datetime]::Today.ToString("yyyy-MM-dd't'HH:mm:ss")       # 'o' for ISO format time may work here.
                       $end   = [datetime]::Today.AddDays($days).tostring("yyyy-MM-dd't'HH:mm:ss")
                       $uri  += "/calendarview?`$expand=calendar&startdatetime=$start&enddatetime=$end"
    }
    else {             $uri  +=  '/events?$expand=calendar'}
    if ($Select)     { $uri  +=  '&$select=' + ($Select -join ',') }
    if ($Subject)    { $uri  += ('&$filter=startswith(subject,''{0}'')' -f $subject ) }
    elseif ($Filter) { $uri  +=  '&$Filter='  + $Filter }
    if ($OrderBy)    { $uri  +=  '&$orderby=' + $orderby }
    if ($Top)        { $uri  +=  '&$top='     + $top  }
    #endregion

    #region get the data. Cope with data being paged, add a type to help formatting and return the results.
    $eventlist      = @()
    $result         = Invoke-RestMethod @webParams -Uri $uri
    $eventlist     += $result.value
    while ($result.'@odata.nextLink') {
        $result     = Invoke-RestMethod @webParams -Uri  $result.'@odata.nextLink' ;
        $eventlist += $result.value
    }

    if ($eventlist) {
        $defaultProperties = @('Subject','When','Where','ShowAs')
        $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
        $psStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
        $whensb = { if (  ([datetime]$this.start.datetime).AddDays(1) -eq ([datetime]$this.End.datetime  )) {
                          ([datetime]$this.start.datetime).ToShortDateString()
                    }
                    else {([datetime]$this.Start.datetime).ToString("g") + ' to ' +  ([datetime]$this.End.datetime  ).ToString("g") + $this.End.timezone}
        }
        foreach ($e in $eventlist) {
            $e.pstypeNames.add('GraphEvent')
            Add-Member -InputObject $e -MemberType ScriptProperty -Name StartDateTime     -Value {[datetime]$this.start.dateTime}
            Add-Member -InputObject $e -MemberType ScriptProperty -Name EndDateTime       -Value {[datetime]$this.end.dateTime}
            Add-Member -InputObject $e -MemberType ScriptProperty -Name When              -Value $whenSB
            Add-Member -InputObject $e -MemberType ScriptProperty -Name Where             -Value {$this.location.displayname}
            Add-Member -InputObject $e -MemberType MemberSet      -Name PSStandardMembers -Value $PSStandardMembers
        }

        $eventlist
    }
    else {Write-Warning -Message "No events were found."}
    #endregion
}

function New-RecurrencePattern   {
    <#
      .synopsis
        Creates a new recurrence pattern for an appointment
       .Description
        There are 6 patterns:
        *Daily, Weekly, Relative & absolute Monthly and
        Relative a& absolute yearly. Each one takes an interval,
        which is 1 by default, meaning the event is scheduled for each
        matching day. 2 would be alternate matchs, 3 would be every
        third one etc. Weekly, and the two relative choice specify
        one or more week days, and realtive ones say which week of the
        month. The two absolute options specify which date in the month;
        and the two yearly options specify which month of the year.
        The pattern needs to know if it should end, so it can be given
        an end date, or a total number of occurences. If neither is
        specified the event runs until removed from the calendar.
      .Example
        >$rec = New-RecurrencePattern -Weekly Friday -EndDate 2019-04-01
        Creates a weekly meeting on every Friday until April 1st 2019
      .Example
        >$rec = New-RecurrencePattern -AbsoluteMonthly -DayOfMonth 1 -Occurrences 12
        Specifies a pattern of a meeting on the first of the month for the next
        12 months.
    #>

    [cmdletbinding()]
    [outputType([system.collections.hashtable])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Does not change system state.')]
    param (

        # Event repeats on the every day or every [-interval] days
        [Parameter(Mandatory=$true,  ParameterSetName='daily')]
        [switch]$Daily,

        # Event repeats on the same day(s) of the week
        [Parameter(Mandatory=$true,  ParameterSetName='weekly')]
        [switch]$Weekly,

        # Event repeats on the same day of the week in the same relative position each month
        [Parameter(Mandatory=$true,  ParameterSetName='relativeMonthly')]
        [switch]$RelativeMonthly,

        # Event repeats on the same date in the month, each month
        [Parameter(Mandatory=$true,  ParameterSetName='absoluteMonthly')]
        [switch]$AbsoluteMonthly,

        #Event happens yearly on the same day week, and same relative position in the month.
        [Parameter(Mandatory=$true,  ParameterSetName='relativeYearly')]
        [switch]$RelativeYearly,

        #Event happens yearly on the same day of the month.
        [Parameter(Mandatory=$true,  ParameterSetName='AbsoluteYearly')]
        [switch]$AbsoluteYearly,

        # Which instance of the the selected day of the week will the event occur on
        [Parameter(Position=1, ParameterSetName='relativeYearly')]
        [Parameter(Position=1, ParameterSetName='relativeMonthly')]
        [ValidateSet('first','second','third','fourth','last')]
        [string]$WeekOfMonth = 'first',

        #On which day of the week will the event occur
        [Parameter(Mandatory=$true, Position=2, ParameterSetName='relativeYearly')]
        [Parameter(Mandatory=$true, Position=2, ParameterSetName='relativeMonthly')]
        [Parameter(Mandatory=$true, Position=1, ParameterSetName='weekly')]
        [ValidateSet('Monday','Tuesday','Wednesday','Thursday','Friday', 'Saturday', 'Sunday')]
        [string[]]$Days,

        #On which date in the month will the event Occur
        [Parameter(Mandatory=$true, Position=1, ParameterSetName='absoluteMonthly')]
        [Parameter(Mandatory=$true, Position=1, ParameterSetName='AbsoluteYearly')]
        [ValidateRange(1,31)]
        $DayOfMonth,

        #In which month does the event occur - as a number, so 9 for September.
        [Parameter(Mandatory=$true, Position=2, ParameterSetName='relativeYearly')]
        [Parameter(Mandatory=$true, Position=2, ParameterSetName='AbsoluteYearly')]
        [ValidateRange(1,12)]
        $Month,

        #How many days, weeks , months, years apart will events occur
        [Parameter(Position=3)]
        [Int]$Interval = 1,

        #Date to stop applying the pattern, the last event may not fall on this date.
        [dateTime]$EndDate,

        #The number of occurences after which the event should cease
        [Int]$Occurrences = 10

    )
    if     ($EndDate)         {$range   = @{type = 'endDate'  ; endDate= $EndDate.ToString('yyyy-MM-dd') }  }
    Elseif ($Occurrences)     {$range   = @{type = 'numbered' ; numberOfOccurrences= $Occurrences}  }
    Else                      {$range   = @{type = 'noEnd'    ; }}

    if     ($Daily)           {$pattern = @{type = 'daily'           ;interval = $Interval} }
    elseif ($Weekly)          {$pattern = @{type = 'weekly'          ;interval = $Interval; daysOfWeek= @()+$Days} }
    elseif ($RelativeMonthly) {$pattern = @{type = 'relativeMonthly' ;interval = $Interval; daysOfWeek= @()+$Days; index=$WeekOfMonth  } }
    elseif ($RelativeYearly)  {$pattern = @{type = 'relativeYearly'  ;interval = $Interval; daysOfWeek= @()+$Days; index=$WeekOfMonth ; month=$Month} }
    elseif ($AbsoluteYearly)  {$pattern = @{type = 'absoluteYearly'  ;interval = $Interval; dayOfMonth= $DayOfMonth               ; month=$Month} }
    elseif ($AbsoluteMonthly) {$pattern = @{type = 'absoluteYearly'  ;interval = $Interval; dayOfMonth= $DayOfMonth} }

    return @{'range'=$range; 'pattern'=$pattern}
}

function New-EventAttendee       {
    <#
      .Synopsis
        Creats a new meeting attendee, with a mail address and the type of attendance.
    #>

    [cmdletbinding(DefaultParameterSetName='Default')]
    [outputType([system.collections.hashtable])]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Does not change system state.')]
    param(
        # The recipient's email address, e.g Alex@contoso.com
        [Parameter(Position=0, ValueFromPipelineByPropertyName=$true,ParameterSetName='Default',Mandatory=$true)]
        $Mail,
        #The displayname for the recipient
        [Parameter(Position=1, ValueFromPipelineByPropertyName=$true,ParameterSetName='Default')]
        $DisplayName,
        #Is the attendee required or optional or a resource (such as a room). Defaults to required
        [ValidateSet('required', 'optional', 'resource')]
        $AttendeeType = 'required',
        [Parameter(ValueFromPipeline=$true,ParameterSetName='PipedStrings',Mandatory=$true)]
        $InputObject
    )
    @{ 'type'= $AttendeeType ; 'emailAddress' = (New-MailAddress -Mail:$mail -DisplayName:$DisplayName )}
}

function Add-GraphEvent          {
    <#
      .Synopsis
        Adds an event to a calendar
      .link
        Get-GraphEvent
      .Example
        >
        >$rec = New-RecurrencePattern -Weekly Friday -EndDate 2019-04-01
        >Add-GraphEvent -Start "2019-01-23 15:30:00" -subject "Enter time sheet" -Recurrence $rec
        Creates a recurring event. The first sets up a weekly schedule for Fridays until April 1st.
        The second sets the time (if no end is given, it is set for 30 minutes after the start),
        the subject, and the recurrence pattern
      .Example
        >
        >$Chris = New-Attendee -Mail Chris@Contoso.com
        >Add-GraphEvent -subject "Requirements for Basingstoke project" -Start "2019-02-02 10:00" -End "2019-02-02 11:00" -Attendees $chris
        Creates a meeting with a second person. The first command creates an attendee - by default the attendee is 'required'
        The second creates the appointment, adding the attendee and sending a meeting request.
      .Example
        >
        >$Chris = New-Attendee -Mail Chris@Contoso.com -display 'Chris Cross' optional
        >$Phil = New-Attendee -Mail Phil@Contoso.com
        >Add-GraphEvent -subject "Phase II planning" -Start "2019-02-02 14:00" -End "2019-02-02 14:30" -Attendees $chris,$phil
        Creates a meeting with a two additonal attendee. The first command creates an optional attendee with a display name
        the second creates an attendee with no displayed name and the default 'required' type
        Finally the meeting is created.
    #>

    [cmdletbinding()]
    param (
        #UserID as a guid or User Principal name, whose calendar should be fetched If not specified defaults to "me"
        [Parameter( ParameterSetName="User",ValueFromPipelineByPropertyName=$true)]
        [string]$User,

        #A sepecific calendar belonging to a user.
        [Parameter( ParameterSetName="User",ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
        $Calendar,

        #Group ID or a Group object with an ID whose calendar should be fetched
        [Parameter(Mandatory=$true, ParameterSetName="Group", ValueFromPipelineByPropertyName=$true)]
        [Alias('Team')]
        $Group,

        #Subject for the appointment
        [string]$Subject ,

        #Start time - if -Timezone is not used this will be the in local machine's times zone
        [Parameter(Mandatory=$true)]
        [datetime]$Start,

        #End Time - if -Timezone is not used this will be the in local machine's times zone
        [datetime]$End,

        #Timezone - by default the local machine's time zone is used
        $Timezone = $(tzutil.exe /g),

        #Creates the event as all day.
        [Switch]$AllDay,

        #Location for the appointment
        $Location,

        #People or resources involved in the event.
        $Attendees,
        #Sets the task to appear as Free, Tenatative, Off-of-facility etc
        [ValidateSet('busy','free','oof','tentative','workingElsewhere')]
        [string]$ShowAs,

        #Unless -Reminder on is specified no reminder will sound before the meeting
        [switch]$ReminderOn,
        #Time in Minutes, before the start time, that the reminder should appear. It will be set even if -ReminderOn is omitted
        $ReminderTime,
        #Body text - if using HTML set the body type to HTML
        $Body  ,
        #Type of text used for the body, Text or HTML
        [ValidateSet('Text','HTML')]
        $BodyType = 'Text',
        #Priority setting , high , normal or low.
        [ValidateSet('low','normal','high')]
        [String]$Importance ,
        #Privacy setting - normal or Private
        [ValidateSet('normal','private')]
        [String]$Sensitivity,

        #Recurrence pattern build with New-recurrencePattern
        $Recurrence
        # for some of things still to do see https://docs.microsoft.com/en-us/graph/api/event-update?view=graph-rest-beta
        # and https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-beta
        # Attendees is one. link says this also sends the invite

    )
    Connect-MSGraph
    $webParams = @{Method     = 'Post'
                  Contenttype = 'application/json'
                  Headers     = @{Authorization = $Script:AuthHeader ;
                                  Prefer        = "Outlook.timezone=""$TimeZone"""}
    }

    #region figure out which calendar to get. The API doesn't have v1.0/calendars/id, have to do group/calendar user/calendar or user/calendarS/id
    if     ($user -and $Calendar) { #get a specific calendar for a specific user
        if ($User.ID)     {$User=$User.ID}
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/users/$user/calendars/$Calendar/events"
    }
    elseif ($User)     {  # get the default calendar for a specific user
        if ($User.ID) {$User=$User.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/users/$user/calendar/events"  #for the default calendar you can also use users/{id}/events "Calendar"
    }
    elseif ($Group)    {  # get the [only] calendar for a group
        if ($Group.ID) {$Group=$Group.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/groups/$Group/calendar/events"   #for the default calendar you can also use groups/{id}/events
    }
    elseif ($Calendar -and $Calendar.GroupID ) { #handle piping in a group's calendar object - we have added the group ID to it, us that get the group's [only] calendar
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/groups/$($calendar.groupID)/calendar/events"
    }
    elseif ($calendar) { #get a specific calendar for the current user - more normal use of the calendar parameter
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/me/calendars/$calendar/events"
    }
    else  {  #no User, group or calendar specified get the current users default calendar.
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/me/calendar/events"   #for the default calendar you can also use me/events
    }
    #endregion

    #region assemble the body needed to create the event
    $settings = @{      'subject'=  $Subject;     'isReminderOn' = [bool]$ReminderOn}
    if ($Location)     {$settings['location']                    = @{'displayName'=$Location} }
    if ($Body)         {$settings['body']                        = @{'contentType'=$BodyType ; 'Content'=$Body}}
    if ($ShowAs)       {$settings['showAs']                      = $ShowAs}
    if ($ReminderTime) {$settings['reminderMinutesBeforeStart']  = $ReminderTime}
    if ($Importance)   {$settings['importance']                  = $Importance}
    if ($Sensitivity)  {$settings['sensitivity']                 = $Sensitivity}
    if ($AllDay)       {$settings['isAllDay']                    = $true
                        $Start = $Start.Date
                        if (-not $End)        { $End             = $Start.AddDays(1)}
                        else                  { $End             = $End.Date        }
                        if ($End -eq $Start)  { $End             = $End.AddDays(1)  }
    }
    elseif (-not $End) {$End = $Start.AddMinutes(30)    }
    $settings['start' ] = @{'timeZone'=$Timezone; 'dateTime'    = $Start.ToString("yyyy-MM-dd'T'HH:mm:ss")} ;
    $settings['end'   ] = @{'timeZone'=$Timezone; 'dateTime'    = $End.ToString(  "yyyy-MM-dd'T'HH:mm:ss")};

    if ($Recurrence)   {$settings['recurrence']                 = $Recurrence
                        $settings.recurrence.range['startDate'] = $Start.ToString('yyyy-MM-dd');
    }
    if ($Attendees)    {$settings['attendees'] = @() + $Attendees }
    $json =  (ConvertTo-Json $settings -Depth 10)
    Write-Debug $json
    #endregion

    $result = Invoke-RestMethod @webParams -Body $json
    if ($PassThru) {
        #send back the new appoinment and give it a type so it will get formatted.
        $result.pstypeNames.add('GraphEvent')

        $result
    }
}

function Set-GraphEvent          {
    <#
      .Synopsis
        Modifies an event on a calendar
      .link
        Get-GraphEvent
      .Example
        TBC
    #>

    [cmdletbinding(SupportsShouldProcess=$true,DefaultParameterSetName='None')]
    param (
        #The event to be updateds either as an ID or as an event object containing an ID.
        [Parameter(ValueFromPipeline=$true,Position=0,Mandatory=$true)]
        $Event,

        #UserID as a guid or User Principal name, whose calendar should be fetched If not specified defaults to "me"
        [Parameter( ParameterSetName="User",ValueFromPipelineByPropertyName=$true)]
        [string]$User,

        #A sepecific calendar belonging to a user.
        [Parameter( ParameterSetName="User",ValueFromPipelineByPropertyName=$true)]
        $Calendar,

        #Group ID or a Group object with an ID whose calendar should be fetched
        [Parameter(Mandatory=$true, ParameterSetName="Group", ValueFromPipelineByPropertyName=$true)]
        [Alias('Team')]
        $Group,

        #Subject for the appointment
        [string]$Subject ,

        #Start time - if -Timezone is not used this will be the in local machine's times zone
        [Parameter( ParameterSetName="Group" )]
        [Parameter( ParameterSetName="None"  )]
        [Parameter( ParameterSetName="User"   )]
        [Parameter( ParameterSetName="AllDay", Mandatory=$true )]
        [Nullable[datetime]]$Start,

        #End Time - if -Timezone is not used this will be the in local machine's times zone
        [Parameter( ParameterSetName="Group"  )]
        [Parameter( ParameterSetName="None"   )]
        [Parameter( ParameterSetName="User"   )]
        [Parameter( ParameterSetName="AllDa#y", Mandatory=$true )]
        [Nullable[datetime]]$End,

        #Creates the event as all day - you must also set the start and end time.
        [Parameter(Mandatory=$true, ParameterSetName="AllDay")]
        [Switch]$AllDay,

        #Timezone - by default the local machine's time zone is used
        $Timezone = $(tzutil.exe /g),
        #Location for the appointment
        $Location,
        #Body text - if using HTML set the body type to HTML
        $Body  ,
        #Type of text used for the body, Text or HTML
        [ValidateSet('Text','HTML')]
        $BodyType = 'Text',
        #Unless -Reminder on is specified no reminder will sound before the meeting
        [switch]$ReminderOn,
        #Time in Minutes, before the start time, that the reminder should appear. It will be set even if -ReminderOn is omitted
        $ReminderTime,
        #Sets the task to appear as Free, Tenatative, Off-of-facility etc
        [ValidateSet('busy','free','oof','tentative','workingElsewhere')]
        [string]$ShowAs,
        #Priority setting , high , normal or low.
        [ValidateSet('low','normal','high')]
        [String]$Importance ,
        #Privacy setting - normal or Private
        [ValidateSet('normal','private')]
        [String]$Sensitivity,

        #Recurrence pattern build with New-recurrencePattern
        $Recurrence,
        #If specified the update will be performed without prompting for confirmation (this is the default)
        [switch]$Force
        # for some of things still to do see https://docs.microsoft.com/en-us/graph/api/event-update?view=graph-rest-beta
        # and https://docs.microsoft.com/en-us/graph/api/user-post-events?view=graph-rest-beta
        # Attendees is one. link says this also sends the invite

    )
    Connect-MSGraph
    $webParams = @{Method     = 'Patch'
                  Contenttype = 'application/json'
                  Headers     = @{Authorization = $Script:AuthHeader ;
                                  Prefer        = "Outlook.timezone=""$TimeZone"""}
    }

    #region figure out which calendar to get. The API doesn't have v1.0/calendars/id, have to do group/calendar user/calendar or user/calendarS/id
    if     ($user -and $Calendar) { #get a specific calendar for a specific user
        if ($User.ID)     {$User=$User.ID}
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/users/$user/calendars/$Calendar/events"
    }
    elseif ($User)   {  # get the default calendar for a specific user
        if ($User.ID) {$User=$User.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/users/$user/calendar/events"  #for the default calendar you can also use users/{id}/events "Calendar"
    }
    elseif ($Group) {  # get the [only] calendar for a group
        if ($Group.ID) {$Group=$Group.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/groups/$Group/calendar/events"   #for the default calendar you can also use groups/{id}/events
    }
    elseif ($Calendar -and $Calendar.GroupID ) { #handle piping in a group's calendar object - we have added the group ID to it, us that get the group's [only] calendar
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/groups/$($calendar.groupID)/calendar/events"
    }
    elseif ($calendar) { #get a specific calendar for the current user - more normal use of the calendar parameter
        If ($Calendar.id) {$Calendar = $Calendar.ID}
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/me/calendars/$calendar/events"
    }
    else  {  #no User, group or calendar specified get the current users default calendar.
        $webParams['uri'] = "https://graph.microsoft.com/v1.0/me/calendar/events"   #for the default calendar you can also use me/events
    }
    #endregion
    if ($Event.id) {$webParams['uri'] += "/$($Event.id)"}
    else           {$webParams['uri'] += "/$Event"}
    $settings  =   @{ }
    #region assemble the body needed to update the event
    if ($PSBoundParameters.ContainsKey('ReminderOn')) {
                           $settings['isReminderOn']    = [bool]$ReminderOn}
    if ($PSBoundParameters.ContainsKey('AllDay'))     {
                           $settings['isAllDay']        = [bool]$AllDay  }
    if ($Subject)         {$settings['subject']         = $subject };
    if ($Location)        {$settings['location']        = @{'displayName'=$Location} }
    if ($Body)            {$settings['body']            = @{'contentType'=$BodyType ; 'Content'=$Body}}
    if ($ShowAs)          {$settings['showAs']          = $ShowAs}
    if ($Importance)      {$settings['importance']      = $Importance}
    if ($Sensitivity)     {$settings['sensitivity']     = $Sensitivity}
    if ($ReminderTime)    {$settings['reminderMinutesBeforeStart'] = $ReminderTime}
    if ($Start)           {$settings['start'] = @{'timeZone'       = $Timezone; 'dateTime' = $Start.ToString("yyyy-MM-dd'T'HH:mm:ss")} }
    if ($End)             {$settings['end'  ] = @{'timeZone'       = $Timezone; 'dateTime' = $End.ToString(  "yyyy-MM-dd'T'HH:mm:ss")} }
    if ($Recurrence)      {$settings['recurrence']                 = $Recurrence
                           $settings.recurrence.range['startDate'] = $Start.ToString('yyyy-MM-dd');
    }
    $json =  (ConvertTo-Json $settings -Depth 10)
    Write-Debug $json
    #endregion

    if ($Force -or $PSCmdlet.ShouldProcess($Event.subject,'Update calendar event')) {
        $result = Invoke-RestMethod @webParams -Body $json
        $result.pstypeNames.add('GraphEvent')
        return $result
    }
}

function Remove-GraphEvent       {
    <#
      .Synopsis
        Deletes an item from the calendar
      .Description
        Deletes items from the calendar. If other people have beeen invited to a meeting,
        they will reveive a cancellation message.
    #>

    [cmdletbinding(DefaultParameterSetName="None",SupportsShouldProcess=$true,ConfirmImpact='High')]
    param   (

        #The event to be removed either as an ID or as an event object containing an ID.
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        $Event,

        #UserID as a guid or User Principal name, whose calendar should be fetched If not specified defaults to "me"
        [Parameter( ParameterSetName="User",Mandatory=$true)]
        [string]$User,

        #A sepecific calendar belonging to a user.
        [Parameter( ParameterSetName="User",Mandatory=$true)]
        $Calendar,

        #Group ID or a Group object with an ID, whose calendar should be fetched
        [Parameter(Mandatory=$true, ParameterSetName="GroupID")]
        [Alias("Team")]
        $Group,

        #if Sepcified the event will be deleted without prompting for confirmation
        [switch]$Force
    )
    begin   {
        Connect-MSGraph
    }
    process {
        if     ($event.calendar.id -and -not $Calendar) {$Calendar = $event.calendar.id}

        if     ($user -and $Calendar) { #get a specific calendar for a specific user
            if ($User.ID)     {$User     = $User.ID}
            If ($Calendar.id) {$Calendar = $Calendar.ID}
            $uri = "https://graph.microsoft.com/v1.0/users/$user/calendars/$Calendar/events"
        }
        elseif ($User)   {  # get the default calendar for a specific user
            if ($User.ID) {$User=$User.ID}
            $uri = "https://graph.microsoft.com/v1.0/users/$user/calendar/events"
        }
        elseif ($Group) {  # get the [only] calendar for a group
            if ($Group.ID) {$Group=$Group.ID}
            $uri = "https://graph.microsoft.com/v1.0/groups/$Group/calendar/events"
        }
        elseif ($calendar) { #get a specific calendar for the current user - more normal use of the calendar parameter
            If ($Calendar.id) {$Calendar = $Calendar.ID}
            $uri = "https://graph.microsoft.com/v1.0/me/calendars/$calendar/events"
        }
        else  {  #no User, group or calendar specified get the current users default calendar.
            $uri = "https://graph.microsoft.com/v1.0/me/calendar/events"
        }
        if ($Force -or $PSCmdlet.ShouldProcess($Event.Subject ,'Delete from calendar')) {
            if ($event.ID) {$event = $event.id}
            Invoke-RestMethod -Method Delete -Uri "$uri/$event" -Headers $Script:DefaultHeader
        }
    }
}