PersonalContacts.ps1


function New-GraphPhysicalAddress {
    <#
      .synopsis
        Builds a street / postal / physical address to use in the contact commands
       .Example
        >$fabrikamAddress = New-GraphPhysicalAddress "123 Some Street" Seattle WA 98121 "United States"
        Creates an address - if the -Street, City, State, Postalcode country are not explictly
        specified they will be assigned in that order. Quotes are desireable but only necessary
        when a value contains spaces.
        It can then be used like this. Set-GraphContact $pavel -BusinessAddress $fabrikamAddress
    #>

    [cmdletbinding()]
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification='Does not change system state.')]
    param   (
        #Street address. This can contain carriage returns for a district, e.g. "101 London Road`r`nBotley"
        [String]$Street,
        #City, or town as people outside the US tend to call it
        [String]$City,
        #State, Province, County, the administrative level below country
        [String]$State,
        #Postal code. Even it parses as a number, as with US ZIP codes, it will be converted to a string
        [String]$PostalCode,
        #Usually a country but could be some other geographical entity
        [String]$CountryOrRegion
    )
    $Address = @{}
    foreach ($P in $PSBoundParameters.Keys.Where({$_ -notin [cmdlet]::CommonParameters})) {
        $Address[$p] + $PSBoundParameters[$p]
    }
    $Address
}

function Get-GraphContact         {
    <#
      .Synopsis
        Get the user's contacts
      .Example
        get-graphContact -name "o'neill" | ft displayname, mobilephone
        Gets contacts where the display name, given name, surname, file-as name, or email begins with
        O'Neill - note the function handles apostrophe, - a single one would normal cause an error with the query.
        The results are displayed as table with display name and mobile number
    #>

    [cmdletbinding(DefaultParameterSetName="None")]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphContact])]
    param   (
        #UserID as a guid or User Principal name. If not specified defaults to "me"
        [string]$User,
        #A custom set of contact properties to select
        [ValidateSet('assistantName', 'birthday', 'businessAddress', 'businessHomePage', 'businessPhones',
                     'categories', 'changeKey', 'children', 'companyName', 'createdDateTime', 'department',
                     'displayName', 'emailAddresses', 'fileAs', 'generation', 'givenName', 'homeAddress',
                     'homePhones', 'id', 'imAddresses', 'initials', 'jobTitle', 'lastModifiedDateTime',
                     'manager', 'middleName', 'mobilePhone',  'nickName', 'officeLocation', 'otherAddress',
                     'parentFolderId', 'personalNotes', 'profession', 'spouseName', 'surname', 'title',
                     'yomiCompanyName', 'yomiGivenName', 'yomiSurname')]
        [string[]]$Select,

        #If specified looks for contacts where the display name, file-as Name, given name or surname beging with ...
        [Parameter(Mandatory=$true, ParameterSetName='FilterByName')]
        [string]$Name,
        #A custom OData Filter String
        [Parameter(Mandatory=$true, ParameterSetName='FilterByString')]
        [string]$Filter
    )

    #region build the URI - if we got a user ID, use it, add select, filter, orderby and/or top as needed
    if     ($User.id) {$uri = "$GraphUri/users/$($User.id)/contacts?`$top=100"}
    elseif ($User)    {$uri = "$GraphUri/users/$User/contacts?`$top=100" }
    else              {$uri = "$GraphUri/me/contacts?`$top=100" }

    if ($Select)      {$uri = $uri + '&$select=' + ($Select -join ',') }
    if ($Name)        {$uri = $uri + '&$filter=' + (FilterString $Name -ExtraFields 'companyname','givenName','surname') }
    if ($Filter)      {$uri = $uri + '&$Filter='  + $Filter   }
    #endregion

    Invoke-GraphRequest -Uri  $uri -ValueOnly -AllValues -AsType ([MicrosoftGraphContact]) -ExcludeProperty "@odata.etag"
}

function New-GraphContact         {
    <#
      .Synopsis
        Adds an entry to the current users Outlook contacts
      .Description
        Almost all the paramters can be accepted form a piped object to make import easier.
       .Example
       >New-GraphContact -GivenName Pavel -Surname Bansky -Email pavelb@fabrikam.onmicrosoft.com -BusinessPhones "+1 732 555 0102"
       Creates a new contact; if no displayname is given, one will be decided using given name and suranme;
       .Example
       >
       >$PavelMail = New-GraphRecipient -DisplayName "Pavel Bansky [Fabikam]" -Mail pavelb@fabrikam.onmicrosoft.com
       >New-GraphContact -GivenName Pavel -Surname Bansky -Email $pavelmail -BusinessPhones "+1 732 555 0102"
        This creates the same contanct but sets up their email with a display name.
        New recipient creates a hash table
        @{'emailaddress' = @ {
                'name' = 'Pavel Bansky [Fabikam]'
                'address' = 'pavelb@fabrikam.onmicrosoft.com'
            }
        }
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphContact])]
    param   (
        [Parameter(ValueFromPipelineByPropertyName)]
        $GivenName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $MiddleName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Initials ,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Surname,
        [Parameter(ValueFromPipelineByPropertyName)]
        $NickName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $FileAs,
        [Parameter(ValueFromPipelineByPropertyName)]
        $DisplayName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $CompanyName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $JobTitle,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Department,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Manager,
        #One or more mail addresses, as a single string with semi colons between addresses or as an array of strings or MailAddress objects created with New-GraphMailAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        $Email,
        #One or more instant messaging addresses, as an array or as a single string with semi colons between addresses
        [Parameter(ValueFromPipelineByPropertyName)]
        $IM,
        #A single mobile phone number
        [Parameter(ValueFromPipelineByPropertyName)]
        $MobilePhone,
        #One or more Business phones either as an array or as single string with semi colons between numbers
        [Parameter(ValueFromPipelineByPropertyName)]
        $BusinessPhones,
        #One or more home phones either as an array or as single string with semi colons between numbers
        [Parameter(ValueFromPipelineByPropertyName)]
        $HomePhones,
        #An address object created with New-GraphPhysicalAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        $Homeaddress,
        #An address object created with New-GraphPhysicalAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        $BusinessAddress,
        #An address object created with New-GraphPhysicalAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        $OtherAddress,
        #One or more categories either as an array or as single string with semi colons between them.
        [Parameter(ValueFromPipelineByPropertyName)]
        $Categories,
        #The contact's Birthday as a date
        [Parameter(ValueFromPipelineByPropertyName)]
        [dateTime]$Birthday ,
        [Parameter(ValueFromPipelineByPropertyName)]
        $PersonalNotes,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Profession,
        [Parameter(ValueFromPipelineByPropertyName)]
        $AssistantName,
        [Parameter(ValueFromPipelineByPropertyName)]
        $Children,
        [Parameter(ValueFromPipelineByPropertyName)]
        $SpouseName,
        #If sepcified the contact will be created without prompting for confirmation. This is the default state but can change with the setting of confirmPreference.
        [Switch]$Force
    )

    process {
        Set-GraphContact @PSBoundParameters -IsNew
    }
}

function Set-GraphContact         {
    <#
      .Synopsis
        Modifies or adds an entry in the current users Outlook contacts
      .Example
        >
        > $pavel = Get-GraphContact -Name pavel
        > Set-GraphContact $pavel -CompanyName "Fabrikam" -Birthday "1974-07-22"
        The first line gets the Contact which was added in the 'New-GraphContact" example
        and the second adds Birthday and Company-name attributes to the contact.
       .Example
        >
        > $fabrikamAddress = New-GraphPhysicalAddress "123 Some Street" Seattle WA 98121 "United States"
        > Set-GraphContact $pavel -BusinessAddress $fabrikamAddress
        This continues from the previous example, creating an address in the first line
        and adding it to the contact in the second.
 
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphContact])]
    param   (
    #The contact to be updated either as an ID or as contact object containing an ID.
    [Parameter(ValueFromPipeline=$true,ParameterSetName='UpdateContact',Mandatory=$true, Position=0 )]
    $Contact,
    #If specified, instead of providing a contact, instructs the command to create a contact instead of updating one.
    [Parameter(ParameterSetName='NewContact',Mandatory=$true )]
    [switch]$IsNew,
    [Parameter(ValueFromPipelineByPropertyName)]
    $GivenName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $MiddleName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Initials ,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Surname,
    [Parameter(ValueFromPipelineByPropertyName)]
    $NickName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $FileAs,
    #If not specified a display name will be generated, so updates without the display name may result in overwriting an existing one
    [Parameter(ValueFromPipelineByPropertyName)]
    $DisplayName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $CompanyName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $JobTitle,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Department,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Manager,
    #One or more mail addresses, as a single string with semi colons between addresses or as an array of strings or MailAddress objects created with New-GraphMailAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    $Email,
    #One or more instant messaging addresses, as an array or as a single string with semi colons between addresses
    [Parameter(ValueFromPipelineByPropertyName)]
    $IM,
    #A single mobile phone number
    [Parameter(ValueFromPipelineByPropertyName)]
    $MobilePhone,
    #One or more Business phones either as an array or as single string with semi colons between numbers
    [Parameter(ValueFromPipelineByPropertyName)]
    $BusinessPhones,
    #One or more home phones either as an array or as single string with semi colons between numbers
    [Parameter(ValueFromPipelineByPropertyName)]
    $HomePhones,
    #An address object created with New-GraphPhysicalAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    $Homeaddress,
    #An address object created with New-GraphPhysicalAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    $BusinessAddress,
    #An address object created with New-GraphPhysicalAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    $OtherAddress,
    #One or more categories either as an array or as single string with semi colons between them.
    [Parameter(ValueFromPipelineByPropertyName)]
    $Categories,
    #The contact's Birthday as a date
    [Parameter(ValueFromPipelineByPropertyName)]
    [nullable[dateTime]]$Birthday ,
    [Parameter(ValueFromPipelineByPropertyName)]
    $PersonalNotes,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Profession,
    [Parameter(ValueFromPipelineByPropertyName)]
    $AssistantName,
    [Parameter(ValueFromPipelineByPropertyName)]
    $Children,
    [Parameter(ValueFromPipelineByPropertyName)]
    $SpouseName,
    #If sepcified the contact will be created without prompting for confirmation. This is the default state but can change with the setting of confirmPreference.
    [Switch]$Force
    )
    begin   {
        $webParams = @{
            'ContentType'    = 'application/json'
            'URI'             = "$GraphUri/me/contacts"
            'AsType'          =  ([Microsoft.Graph.PowerShell.Models.MicrosoftGraphContact])
            'ExcludeProperty' = @('@odata.etag', '@odata.context' )
        }
    }
    process {
        $contactSettings = @{  }
        if ($Email)                           {$contactSettings['emailAddresses'] = @() }
        if ($Email -is [string])              {$Email = $Email -split '\s*;\s*'}
        foreach ($e in $Email) {
            if     ($e.emailAddress)          {$contactSettings.emailAddresses    += $e.emailAddress   }
            elseif ($e -is [string])          {$contactSettings.emailAddresses    += @{'address' = $e} }
            else                              {$contactSettings.emailAddresses    += $e  }
        }
        if     ($IM             -is [string]) {$contactSettings['imAddresses']     = @() + $IM             -split '\s*;\s*'}
        elseif ($IM                         ) {$contactSettings['imAddresses']     =       $IM}
        if     ($Categories     -is [string]) {$contactSettings['categories']      = @() + $Categories     -split '\s*;\s*'}
        elseif ($Categories                 ) {$contactSettings['categories']      =       $Categories}
        if     ($Children       -is [string]) {$contactSettings['children']        = @() + $Children       -split '\s*;\s*'}
        elseif ($Children                   ) {$contactSettings['children']        =       $Children}
        if     ($BusinessPhones -is [string]) {$contactSettings['businessPhones']  = @() + $BusinessPhones -split '\s*;\s*'}
        elseif ($BusinessPhones             ) {$contactSettings['businessPhones']  =       $BusinessPhones}
        if     ($HomePhones     -is [string]) {$contactSettings['homePhones']      = @() + $HomePhones     -split '\s*;\s*'}
        elseif ($HomePhones                 ) {$contactSettings['homePhones']      =       $HomePhones  }
        if     ($MobilePhone                ) {$contactSettings['mobilePhone']     =       $MobilePhone}
        if     ($GivenName                  ) {$contactSettings['givenName']       =       $GivenName}
        if     ($MiddleName                 ) {$contactSettings['middleName']      =       $MiddleName}
        if     ($Initials                   ) {$contactSettings['initials']        =       $Initials}
        if     ($Surname                    ) {$contactSettings['surname']         =       $Surname}
        if     ($NickName                   ) {$contactSettings['nickName']        =       $NickName}
        if     ($FileAs                     ) {$contactSettings['fileAs']          =       $FileAs}
        if     ($DisplayName                ) {$contactSettings['displayName']     =       $DisplayName}
        if     ($Manager                    ) {$contactSettings['manager']         =       $Manager}
        if     ($JobTitle                   ) {$contactSettings['jobTitle']        =       $JobTitle}
        if     ($Department                 ) {$contactSettings['department']      =       $Department}
        if     ($CompanyName                ) {$contactSettings['companyName']      =      $CompanyName}
        if     ($PersonalNotes              ) {$contactSettings['personalNotes']   =       $PersonalNotes}
        if     ($Profession                 ) {$contactSettings['profession']      =       $Profession}
        if     ($AssistantName              ) {$contactSettings['assistantName']   =       $AssistantName}
        if     ($Children                   ) {$contactSettings['children']        =       $Children}
        if     ($SpouseName                 ) {$contactSettings['spouseName']      =       $spouseName}
        if     ($Homeaddress                ) {$contactSettings['homeaddress']     =       $Homeaddress}
        if     ($BusinessAddress            ) {$contactSettings['businessAddress'] =       $BusinessAddress}
        if     ($OtherAddress               ) {$contactSettings['otherAddress']    =       $OtherAddress}
        if     ($Birthday                   ) {$contactSettings['birthday']        =       $Birthday.tostring('yyyy-MM-dd')} #note this is a different date format to most things !

        $webParams['body'] = ConvertTo-Json $contactSettings
        Write-Debug $webParams.body
        if ($IsNew) {
            if ($force -or $PSCmdlet.ShouldProcess($DisplayName,'Create Contact')) {
                Invoke-GraphRequest @webParams -method Post
            }
        }
        else {#if Contact Passed
            if ($force -or $PSCmdlet.ShouldProcess($Contact.DisplayName,'Update Contact')) {
                if ($Contact.id)   {$webParams.uri += '/' + $Contact.ID}
                else               {$webParams.uri += '/' + $Contact }
                Invoke-GraphRequest @webParams -method Patch
            }
        }
    }
}

function Remove-GraphContact      {
    <#
      .synopsis
         Deletes a contact from the default user's contacts
      .Example
        > Get-GraphContact -Name pavel | Remove-GraphContact
        Finds and removes any user whose given name, surname, email or display name
        matches Pavel*. This might return unexpected users, fortunately there is a prompt
        before deleting - the prompt it can be supressed by using the -Force switch if you
        are confident you have the right contact selected.
    #>

    [cmdletbinding(SupportsShouldProcess=$true,ConfirmImpact='High')]
    param   (
        #The contact to remove, as an ID or as a contact object containing an ID
        [parameter(Position=0,ValueFromPipeline=$true,Mandatory=$true )]
        $Contact,
        #If specified the contact will be removed without prompting for confirmation
        $Force
    )
    process {
        if ($force -or $pscmdlet.ShouldProcess($Contact.DisplayName, 'Delete contact')) {
            if ($Contact.id) {$Contact = $Contact.id}
            Invoke-GraphRequest -Method Delete -uri "$GraphUri/me/contacts/$Contact"
        }
    }
}