Outlook-Contacts.ps1

function Get-GraphContactList    {
    <#
      .Synopsis
        Get the user's contacts
      .Example
        get-graphContacts -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")]
    param(
        #UserID as a guid or User Principal name. If not specified defaults to "me"
        [string]$UserID,
        #If specified selects the first n contacts
        [int]$Top,
        #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,
        #A custom OData Sort string.
        [string]$OrderBy,
        #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
    )
    Connect-MSGraph
    $webParams = @{Method = "Get"
                   Headers = $Script:DefaultHeader
    }
    #region build the URI - if we got a user ID, use it, add select, filter, orderby and/or top as needed
    if ($UserID)  {$uri = "https://graph.microsoft.com/v1.0/users/$userID/contacts" }
    else          {$uri = "https://graph.microsoft.com/v1.0/me/contacts" }

    $JoinChar = "?" #will next parameter be added to the URI with a "?" or a "&" ?
    if ($Select)  { $uri = $uri + '?$select=' + ($Select -join ',')    ; $JoinChar = "&" }
    if ($Name)    { $uri = $uri + $JoinChar + ("`$filter=startswith(displayName,'{0}') or startswith(givenName,'{0}') or startswith(surname,'{0}') or startswith(fileAs,'{0}')" -f ($Name -replace "'","''" )  )
                  $JoinChar = "&"
    }
    if ($Filter)  { $uri = $uri + $JoinChar + '$Filter='  + $Filter  ; $JoinChar = "&" }
    if ($OrderBy) { $uri = $uri + $JoinChar + '$orderby=' + $orderby ; $JoinChar = "&" }
    if ($Top)     { $uri = $uri + $JoinChar + '$top='     + $top}
    #endregion

    #region get the data - cope with it being paged - add a type for fomatting, and return it
    $ContactList      = @()
    $result           = Invoke-RestMethod @WebParams -Uri  $uri
    $ContactList     += $result.value
    while ($result.'@odata.nextLink') {
        $result       = Invoke-RestMethod @webParams -Uri  $result.'@odata.nextLink'
        $ContactList += $result.value
    }
    $defaultProperties = @('displayname','jobtitle','companyname','mail','mobile','business','home')
    $defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet('DefaultDisplayPropertySet',[string[]]$defaultProperties)
    $psStandardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)

    foreach ($c in $ContactList) {
        $c.pstypenames.add("GraphContact")}
        Add-Member -InputObject $C -MemberType MemberSet      -Name PSStandardMembers -Value $PSStandardMembers
        Add-Member -InputObject $C -MemberType AliasProperty  -Name mobile            -Value 'mobilephone'
        Add-Member -InputObject $C -MemberType ScriptProperty -Name business          -Value {$this.businessPhones[0]}
        Add-Member -InputObject $C -MemberType ScriptProperty -Name home              -Value {$this.HomePhones[0]}
        return $ContactList
    #endregion
}

function New-ContactAddress {
    <#
      .synopsis
        Builds a street / postal / physical address to use in the contact commands
       .Example
        >$fabrikamAddress = New-ContactAddress "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.
        The address is a hash table (you can build your own) and looks like this, although the fields may
        be in any order.
        Name Value
        ---- -----
        street 123 Some Street
        city Seattle
        state WA
        postalCode 98121
        countryOrRegion United States
        It can then be used like this. Set-GraphContact $pavel -BusinessAddress $fabrikamAddress
    #>

    [cmdletbinding()]
    [outputType([system.collections.hashtable])]
    [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 it will be converted to a string
        [String]$PostalCode,
        #Usually a country but could be some other geographical entity
        [String]$CountryOrRegion
    )
    $Address  = @{}
    if ($Street)           {$Address['street']           = $Street}
    if ($City)             {$Address['city'  ]           = $City}
    if ($State)            {$Address['state' ]           = $State}
    if ($PostalCode)       {$Address['postalCode' ]      = $PostalCode}
    if ($CountryOrRegion)  {$Address['countryOrRegion' ] = $CountryOrRegion}
    return $Address
}


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-Recipient -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)]
    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 instant messaging addresses, as a single string with semi colons between addresses or as an array of strings or MailAddress objects created with New-MailAddress
        [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-ContactAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        [Hashtable]$Homeaddress,
        #An address object created with New-ContactAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        [Hashtable]$BusinessAddress,
        #An address object created with New-ContactAddress
        [Parameter(ValueFromPipelineByPropertyName)]
        [Hashtable]$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-GraphContactList -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-ContactAddress "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)]
    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 instant messaging addresses, as a single string with semi colons between addresses or as an array of strings or MailAddress objects created with New-MailAddress
    [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-ContactAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    [Hashtable]$Homeaddress,
    #An address object created with New-ContactAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    [Hashtable]$BusinessAddress,
    #An address object created with New-ContactAddress
    [Parameter(ValueFromPipelineByPropertyName)]
    [Hashtable]$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
    )
    begin   {
        Connect-MSGraph
        $webParams = @{Headers     =  $Script:DefaultHeader
                      ContentType = 'application/json'
                      URI         = 'https://graph.microsoft.com/v1.0/me/contacts'
        }
    }
    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} }
            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 !

        $json = ConvertTo-Json $contactSettings
        Write-Verbose $json
        if ($IsNew) {
            if ($force -or $PSCmdlet.ShouldProcess($DisplayName,'Create Contact')) {
                $result = Invoke-RestMethod @webParams -method Post  -Body $json
                $result.pstypenames.add("GraphContact")
                return $result
            }
        }
        else {#if ContactPassed
            if ($force -or $PSCmdlet.ShouldProcess($Contact.DisplayName,'Update Contact')) {
                if ($Contact.id) {$webParams.uri += '/' + $Contact.ID}
                else             {$webParams.uri += '/' + $Contact }
                $result = Invoke-RestMethod @webParams -method Patch -Body $json
                $result.pstypenames.add("GraphContact")
                return $result
            }
        }
    }
}

function Remove-GraphContact {
    <#
      .synopsis
         Deletes a contact from the detaul user's contacts
      .Example
        > Get-GraphContactList -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
    )
    begin {
        Connect-MSGraph
    }
    process {
        if ($force -or $pscmdlet.ShouldProcess($Contact.DisplayName, 'Delete contact')) {
            if ($Contact.id) {$Contact = $Contact.id}
            Invoke-RestMethod -Method Delete -Headers $Script:DefaultHeader -uri "https://graph.microsoft.com/v1.0/me/contacts/$Contact"
        }
    }
}