Notes.ps1

using namespace System.Management.Automation
using namespace Microsoft.Graph.PowerShell.Models

#To do allow Copy-GraphOneNotePage to specify a destination-section by name (with a completer - which requires a destination notebook parameter which may cause a conflict with page which doesn't have the notebook)
function Set-GraphOneNoteHome    {
    <#
      .synopsis
        Sets a default notebook (and optionally section). Set to $Null to clear the setting
      .example
        >Get-GraphGroup 'Consultants' -Notebooks | Get-GraphOneNoteBook -SectionName general* | Set-GraphOneNoteHome -Verbose
        The first command in the pipeline gets the notebook for the consultants group ,
        the second finds the section in the notebook with an display name beginning "general"
        and the third sets the default section for Add-FileToGraphOneNote, Add-GraphOneNotePage,
        Get-GraphOneNotePage, and Out-GraphOneNote to the this section, and sets the
        default Notebook for All the GraphOneNoteBook and all the GraphOneNoteSection commands
        to the consultants group's notebook.
    #>


    param    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$true,Position=0)]
        [AllowNull()]
        #A note book or notebook section to set as the default location for oneNoteCommands. Passing Null will clear the default.
        $Notebook
    )
    @("*GraphOneNoteBook*:Notebook", '*GraphOneNoteSection*:Notebook', "Add-FileToGraphOneNote:Section",
    "Add-GraphOneNotePage:Section", "Get-GraphOneNotePage:Section", "Out-GraphOneNote:Section") | ForEach-Object {
       $null = $Global:PSDefaultParameterValues.Remove($_)
    }

    if ($notebook -is [MicrosoftGraphOnenoteSection]) {
        Write-Verbose "Setting Default Section to $($notebook.displayname) ... "
       $Global:PSDefaultParameterValues["Add-FileToGraphOneNote:Section"] = $Notebook
       $Global:PSDefaultParameterValues["Add-GraphOneNotePage:Section"]   = $Notebook
       $Global:PSDefaultParameterValues["Get-GraphOneNotePage:Section"]   = $Notebook
       $Global:PSDefaultParameterValues["Out-GraphOneNote:Section"]       = $Notebook
       $Notebook = $Notebook.parentNotebook
    }
   if ($Notebook -is [Microsoft.Graph.PowerShell.Models.MicrosoftGraphNotebook]) {
       Write-Verbose "Setting Default Notebook to $($notebook.displayname)"
       $Global:PSDefaultParameterValues["*GraphOneNoteBook*:Notebook"]    = $Notebook
       $Global:PSDefaultParameterValues['*GraphOneNoteSection*:Notebook'] = $Notebook
       $Global:PSDefaultParameterValues["*GraphOneNotePage*:Notebook"]    = $Notebook
   }
}

function Get-GraphOneNoteBook    {
    <#
      .Synopsis
        Gets notebook objects or sections of notebooks
      .Description
        If run with no parameters it will return the current user's personal notebooks.
        If run with just a -Notebook parameter it will return that notebook (which might belong to a group)
        If run with -Notebook and -Sections it will return the sections in that notebook,
        And if run with just -Sections it will return all the sections in the user's personal notebooks.
      .Example
        >Get-GraphOneNoteBook team
        Looks for a workbook with a displayname begining "team" in the users workbooks. the search is case insensitive.
      .Example
        >Get-GraphOneNoteBook -SectionName Powershell
        Finds a "PowerShell" secion in any of the users workbooks. Again the search is case insensitive
      .Example
        >Get-GraphTeam 'Consultants' -Notebooks | Set-GraphHomeNotebook
        >Get-GraphOneNoteBook -AllSections
        The first command changes the default notebook and selects different sections from the the previous command
    #>

    [cmdletbinding(DefaultParameterSetName="None")]
    [Alias('Get-GraphNoteBook')]
    param    (
        #A graph URI pointing to the notebook, or a notebook object where the .self property is a graph URI...
        $Notebook ,
        [Parameter(ValueFromPipeline=$true,DontShow=$true)]
        $InputObject,
        #If specified returns the sections of the notebook.
        [switch]$AllSections,
        #if specified filters the returned objects by to those with names begining with ...
        [ArgumentCompleter([OneNoteSectionCompleter])]
        [string]$SectionName
    )
    process  {
        if ($InputObject) {$Notebook = $InputObject}
        $msg = "Getting Information about Notebook $($Notebook.DisplayName)"
        Write-Progress $msg
        #convert notebook object with self property to a string. If we don't have a string ager this something is wrong
        if  ($Notebook.self) {$Notebook=$Notebook.self}
        if  ($Notebook -and $Notebook -isnot [string] ) {
            Write-Warning "Invalid notebook parameter" ; return
        }
        $webparams = @{
            'AsType'                = ([MicrosoftGraphNotebook])
            'ExcludeProperty'       = '@odata.context', 'sections@odata.context'
        }
        #If it didn't come in as sting or an object with a self parameter bail out.
        #if it is a path to a notebook use that
        if ($Notebook -and $Notebook -match "$GraphUri/.*/onenote/notebooks/.+") {
                $webparams['uri']       = "$Notebook`?`$expand=Sections"
            }
        else {
            $webparams['valueonly'] = $true
            $webparams['uri']       = "$GraphUri/me/onenote/notebooks?`$expand=Sections"
            if ($Notebook) { #it had a notebook parameter as a string but it wasn't a path so look for it by name
                $webparams['uri']  += ('&$filter=startswith(tolower(displayname),''{0}'')' -f ($Notebook.ToLower() -replace '\*$',''))
            }
        }
        $response = Invoke-GraphRequest @webparams
        #Sections fetched this way won't have parentNotebook, so make sure it is available when needed
        foreach ($bookobj in $response) {
            foreach ($s in $bookobj.sections) { $s.parentNotebook = $bookobj }
            if     ($AllSections) {$bookobj.Sections}
            elseif ($SectionName) {$bookobj.Sections.where({$_.displayname -like $SectionName})}
            else                  {$bookobj}
        }
        Write-Progress $msg -Completed
    }
}

function Get-GraphOneNoteSection {
    <#
      .Synopsis
        Gets details of sections in OneNote notebooks or their pages
     .Description
        This command interogates https://graph.microsoft.com/v1.0
            /users/{id}/onenote/notebooks/{id}/sections
        or /groups/{id}/onenote/notebooks/{id}/sections
        or /sites/{id}/onenote/notebooks/{id}/sections
        which requires consent to use the Notes.Create or Notes.Read scope or better.
        If given a Notebook parameter it returns the sections in the notebook.
        If given a section parameter it either returns details of the section, or
        if the -Pages or -Name Parameters are given returns pages from the section
      .Example
        >$notebook = Get-GraphTeam consultants -Notebooks
        >$notebook.sections[0] | Get-GraphOneNoteSection -PageTitle change
        The first line gets the Notebooks object for the 'consultants' team. This object
        has a 'sections' collection. The second line uses pipes a member of this collection as the
        into Get-GraphOneNoteSection to return the pages in the first section, with the title begining "change".
      .Example
        >Get-GraphOneNoteSection private -notebook $notebook -allpages
        In this example the notebook used in the first example is passed as a notebook is piped into command to get a section, by contrast with the previous section
 
      .Example
        >Get-GraphOneNoteSection -Section $section -Pages -Name "test" | Remove-GraphOneNotePage -Force
        >Gets all pages with names that begin 'Test...' and removes
      $section may be the a section object (from the Sections collection of a notebook object, or
      form Get-GraphOneNotebook -Sections ) or the URL for a section.
    #>

    [cmdletbinding()]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnenotePage],ParameterSetName='Pages')]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnenoteSection])]
    [Alias('Get-GraphNoteBookSection')]
    param    (
        #A graph URI pointing to the section, or a section object where the .self property is a graph URI or a section name...
        [Parameter(Mandatory=$true, ValueFromPipeline=$true,Position=0)]
        [ArgumentCompleter([OneNoteSectionCompleter])]
        $Section ,
        [Parameter()]
        #The notebook to query for section(s) if sections is empty or contains a name
        $Notebook ,
        #If specified, returns the pages in the section(s).
        [switch]$AllPages,
        #If specified filters pages or Sections to those with names beginning ...
        [string]$PageTitle
    )
    process  {
        $msg = "Getting Information about Notebook section $($section.DisplayName)"
        Write-Progress $msg
        if     ($Section -is [MicrosoftGraphNotebook]) {$Notebook = $Section}
        elseif ($Section.self) {$Section = $Section.self}
        if     ($Notebook -and  ($Section -isnot [string] -or $section -Notmatch "^$graphuri.*/onenote/sections/.+")) {
            #A notebook has sections URL we'll use it. If not if it's an object with a self parameter try with that, otherwise if it is a string, assume it's the URI for the notebook
            if     ($Notebook.sectionsUrl)  {$uri  = $Notebook.sectionsUrl  +  '?$expand=parentNotebook'}
            elseif ($Notebook.self)         {$uri  = $Notebook.self + '/sections?$expand=parentNotebook'}
            elseif ($Notebook -is [string]) {$uri  = $Notebook      + '/sections?$expand=parentNotebook'}
            else   {Write-warning -Message 'Could not process the notebook parameter provided' }
            if     ($Section -is [string])  {$uri += ('&$filter=startswith(tolower(displayname),''{0}'')' -f ($Section.ToLower() -replace '\*$','')) }
            $results = Invoke-GraphRequest -Uri $uri -ValueOnly  -AsType ([MicrosoftGraphOnenoteSection]) -ExcludeProperty 'parentSectionGroup@odata.context',  'parentNotebook@odata.context'
            if      ($AllPages) {
                  $results | Get-GraphOneNoteSection -AllPages
                  return
            }
            elseif ($PageTitle) {
                  $results | Get-GraphOneNoteSection -PageTitle $PageTitle
                  return
            }
            else {return $results}
        }
        if     ($Section -isnot [string]  -or $Section -Notmatch "^$graphuri.*/onenote/sections/.+") {
                    Write-Warning 'Can not process the Section Parameter' ; Return
        }
        if     ($PageTitle -or $AllPages) {
            # $expand in the REST API ignores ParentNotebook so if it is empty we'll populate it
            if ($PageTitle) {$uri =  $Section + ('/Pages?$expand=parentSection,ParentNotebook&$filter=startswith(tolower(title),''{0}'')'  -f
                                                  $PageTitle.ToLower() ) }
            else            {$uri =  $Section +  '/Pages?$expand=parentSection,ParentNotebook'}
            Invoke-GraphRequest -Uri $uri -ValueOnly  -AsType ([MicrosoftGraphOnenotepage]) -PropertyNotMatch '@odata.context' |
                ForEach-Object {
                        if ((-not $_.ParentNotebook.DisplayName) -and $PSBoundParameters.section.parentnotebook) {
                                  $_.parentNotebook = $PSBoundParameters.section.parentnotebook
                        }
                $_
            }
            return
        }
        else   {
            Invoke-GraphRequest -Uri  ($Section + '?$expand=parentNotebook')  -AsType ([MicrosoftGraphOnenoteSection]) -ExcludeProperty 'parentSectionGroup@odata.context',  'parentNotebook@odata.context', '@odata.context'
        }
        Write-Progress $msg -Completed
    }

}

function New-GraphOneNoteNotebook {
    #>
    [cmdletbinding(SupportsShouldProcess=$true)]
    param   (
        [Parameter(Mandatory=$true)]
        $Name ,
        [Parameter(ValueFromPipeline=$true)]
        $Team,
        $Site,
        #If specified, the command will run without asking for confirmation; this is the default unless Confirm Preference has been set
        [switch]$Force
    )
    process {
        $webParams = @{
            'Method'           = 'Post'
            'ContentType'      = 'application/json'
            'AsType'           = [MicrosoftGraphOnenote]
            'PropertyNotMatch' = '@odata.context'
        }
        if ($Team -and -not $Team.id) {$Team = Get-GraphTeam $Team}
        if ($Team.id) {
            $webParams['uri'] = "$GraphUri/groups/$($Team.id)/onenote/notebooks"
        }
        elseif ($PSBoundParameters['Team'] -and -not $teamID) {
            throw 'Could not get ID for team'; return
        }
        if ($site.ID) {
            $webParams['uri'] = "$GraphUri/sites/$($site.id)/onenote/notebooks"
        }
        elseif ($site) {
            throw 'Site needs to be an object with an ID'; return
        }
        elseif (-not $site -and -not $team) {
            $webParams['uri'] = "$GraphUri/me/onenote/notebooks"
        }
        $webparams['body']  = ConvertTo-Json @{"displayName" = $name}
        Write-Debug $webparams['body']
        if ($Force -or $PSCmdlet.ShouldProcess($SectionName,"Add new Notebook $Name")) {
            Invoke-GraphRequest @webParams
        }
    }
}

function New-GraphOneNoteSection {
    <#
      .Synopsis
        Adds a section to a OneNote notebook
      .Description
        This command Posts to https://graph.microsoft.com/v1.0
            /users/{id}/onenote/notebooks/{id}/sections
        or /groups/{id}/onenote/notebooks/{id}/sections
        or /sites/{id}/onenote/notebooks/{id}/sections
        which requires consent to use the Notes.Create or Notes.ReadWrite scope or better.
      .OUTPUTS
        Returns an object representing the new section
     .Example
        >
        >$notebook = Get-GraphTeam -ByName accounts -Notebooks
        >$section = New-GraphOneNoteSection -Notebook $notebook -SectionName "FY-19 Year End"
        >Add-GraphOneNotePage -Section $section -HTMLPage '<html><head><title>Welcome</Title></head><body><p>This section is ready for you to add your pages.</p></body></html>'
 
        The first command gets the team notebook for the account team; the second adds a section to it
        and the third adds a welcome page to the new section.
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    [Alias('New-GraphNoteBookSection')]
    param   (
        #A graph URI pointing to the notebook, or a notebook object, this can be set by Set-GraphOneNoteHome
        [Parameter(Mandatory=$true)]
        $Notebook ,
        #Name for the new section.
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        $SectionName,
        #If specified, the command will run without asking for confirmation; this is the default unless Confirm Preference has been set
        [switch]$Force
    )
    begin   {
        if     ($Notebook -is [String] -and
             $Notebook -match "$GraphUri/.*/notebooks/[-\w+]+" ) {
             $Notebook = Invoke-GraphRequest $Matches[0] -ExcludeProperty '@odata.context' -AsType ([MicrosoftGraphNotebook])
        }
        elseif ($Notebook.self -and $Notebook -isnot [MicrosoftGraphNotebook] ) {
                $Notebook = Invoke-GraphRequest $Notebook.Self -ExcludeProperty '@odata.context' -AsType ([MicrosoftGraphNotebook])
        }

        if     ($Notebook -isnot [MicrosoftGraphNotebook] ){
                Write-Warning -Message 'Could not process the Notebook parameter'; Return
        }
        $webParams = @{
            'uri'              =  $Notebook.sectionsUrl
            'Method'           = 'Post'
            'ContentType'      = 'application/json'
            'AsType'           = [MicrosoftGraphOnenoteSection]
            'PropertyNotMatch' = '@odata.context'
        }
    }
    process {
        $webparams['body']  = ConvertTo-Json @{"displayName" = $SectionName}
        Write-Debug $webparams['body']
        if ($Force -or $PSCmdlet.ShouldProcess($SectionName,"Add section to Notebook $($Notebook.displayname)")) {
            $sectionobj = Invoke-GraphRequest @webParams
            $sectionobj.parentNotebook = $Notebook
            return $sectionobj
        }
    }
}

function Get-GraphOneNotePage    {
    <#
      .Synopsis
        Gets a OneNote page's metadata or content
      .Description
        This command interogates https://graph.microsoft.com/v1.0
            /users/{id}/onenote/notebooks/{id}/sections/{id}/pages
        or /groups/{id}/onenote/notebooks/{id}/sections/{id}/pages
        or /sites/{id}/onenote/notebooks/{id}/sections/{id}/pages
        which requires consent to use the Notes.Read scope or better.
        It can get either the page metadata, the page content, or
        the page content marked up with IDs to update the page.
    #>

    [cmdletbinding(DefaultParameterSetName='None')]
    [outputtype([Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnenotePage])]
    [Alias('Get-GraphNoteBookPage')]
    param   (
        #A graph URI pointing to the page, or a page object where the .self property is a graph URI...
        [Parameter(ParameterSetName='Page',              Mandatory=$true, ValueFromPipeline=$true,Position=0)]
        [Parameter(ParameterSetName='PageContent',       Mandatory=$true, ValueFromPipeline=$true,Position=0)]
        [Parameter(ParameterSetName='PageContentWithIDs',Mandatory=$true, ValueFromPipeline=$true,Position=0)]
        [Parameter(ParameterSetName='PagePreview',       Mandatory=$true, ValueFromPipeline=$true,Position=0)]
        $Page,

        #A graph URI pointing to a notebook, or a notebook object. this can be set by Set-GraphOneNoteHome
        [Parameter()]
        $Notebook,

        #A graph URI pointing to a section, or a Section object this can be set by Set-GraphOneNoteHome
        [Parameter()]
        [ArgumentCompleter([OneNoteSectionCompleter])]
        $Section,

        #If specified returns the contents of the page. Ignored if ContentWithIDs is specified
        [Parameter(ParameterSetName='PageContent',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
        [switch]$Content,

        #If specified returs the contents with guids for each section where content can be inserted.
        [Parameter(ParameterSetName='PageContent')]
        [Parameter(ParameterSetName='PageContentWithIDs',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
        [switch]$ContentWithIDs,

        #If specified returs a text preview of the page
        [Parameter(ParameterSetName='PagePreview',Mandatory=$true,ValueFromPipelineByPropertyName=$true)]
        [switch]$PreviewText,

        #If specified writes the preview or content to a file
        [Parameter(ParameterSetName='PageContent')]
        [Parameter(ParameterSetName='PageContentWithIDs')]
        [Parameter(ParameterSetName='PagePreview')]
        $SavePath
    )
    process {
        $webparams = @{
           'AsType'           =  ([Microsoft.Graph.PowerShell.Models.MicrosoftGraphOnenotePage])
           'PropertyNotMatch' = '@odata'
        }
        if     ($Page -is [MicrosoftGraphOnenoteSection]) {$Section = $Page}
        elseif ($Page.self)            {$Page=$Page.self}
        if     ($Section -and ($Page -isnot [string] -or $page -notmatch "$graphuri/.*/onenote/pages/.+" )) {
                if     ($Section.self) {
                        $webParams['uri'] =   "$($Section.self)/pages?`$expand=parentsection(`$expand=ParentNotebook)"
                }
                elseif ($Section -is [string] -and $Section -match "^$graphuri.*/onenote/sections/.+") {
                        $webParams['uri'] =   "$Section/pages?`$expand=parentsection(`$expand=ParentNotebook)"
                }
                elseif ($Section -is [string] -and $Notebook) {
                        $Section = Get-GraphOneNoteSection -Section $Section -Notebook $Notebook
                         if (-not $Section -or $Section.Count -gt 1) {
                            Write-Warning "Could not resolve $Section to a unique section in the notebook."
                        }
                        else {$webParams['uri'] =   "$($Section.self)/pages?`$expand=parentsection(`$expand=ParentNotebook)"}
                }
                else {Write-Warning "Can't resolve the section given"; return }
                if ($Page -is [string]) {
                    $webParams['uri'] += '&$filter=startswith(tolower(title),''{0}'')' -f ($Page.ToLower() -replace '\*$','')
                }
                $pages     = Invoke-GraphRequest @webparams -ValueOnly
                if ($ContentWithIDs -or $Content -or $PreviewText ) {
                      $null = $PSBoundParameters.Remove('Page'), $PSBoundParameters.Remove('Notebook'), $PSBoundParameters.Remove('section')
                      $pages | Get-GraphOneNotePage @PSBoundParameters
                      return
                }
                else {
                    foreach ($p in $pages)   {
                        if ((-not $p.ParentNotebook.DisplayName) -and $Section.parentnotebook) {
                                  $p.parentNotebook = $Section.parentnotebook
                        }
                        $p
                    }
                    return
                }
        }
        elseif ($Page -isnot [string] -or $page -notmatch "$graphuri/.*/onenote/pages/.+") {
                Write-Warning -Message 'Could not process the page parameter' ; return
        }
        if     ($PreviewText -and -not $PSBoundParameters['savepath']) {
               return (Invoke-GraphRequest -Uri  "$Page/Preview").previewText
        }
        elseif ($PreviewText)          {
               (Invoke-GraphRequest -Uri  "$Page/Preview").previewText | Out-File $savePath
               return
        }
        elseif (-not ($ContentWithIDs -or $Content))           {
          Invoke-GraphRequest -Uri $page @webparams
          return
        }
        elseif (      $ContentWithIDs) {$uri = "$Page/Content?includeIDs=true"}
        else                           {$uri ="$Page/Content"}

        #Page content breaks the JSON parser, so ask for it to go to a file instead. If didn't want a file, read the file back and delete it.
        if     (-not $PSBoundParameters['savepath']) {$SavePath = [system.io.path]::GetTempFileName() }
        Invoke-GraphRequest -Uri  $uri  -OutputFilePath $SavePath
        if  (-not $PSBoundParameters['savepath']) {Get-Content $SavePath; Remove-Item $SavePath}
        #should return the outer xml property as this is HTML in an XML document. Check what else it comes back as
    }
}

function Copy-GraphOneNotePage   {
<#
  .synopsis
    Copies a one note page to a different section in the same notebook.
#>

    [Alias('Copy-GraphNoteBookPage')]
    param   (
        #The page to be copied.
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
        $Page,
        #The section the it will be copied to
        [Parameter(Position=1,Mandatory=$true)]
        $DestinationSection,
        #The group which owns the notebook if required
        $GroupID,
        [switch]$Wait
    )
    process {
        $webparams =@{
            'Method'          = 'POST'
            'ContentType'     = 'application/json'
            'ExcludeProperty' = @('@odata.context','@odata.type')
            'AsType'          = ([MicrosoftGraphOnenoteOperation])

        }
        if     ($Page.self)            {$webparams['uri']="$($Page.self)/copyToSection"}
        elseif ($Page -is [string])    {$webparams['uri']="$Page/copyToSection"}
        else   {Write-Warning -Message 'Could not process the page parameter' ; return}

        if     ($DestinationSection.id)           {$settings = @{id=$DestinationSection.id }}
        elseif ($DestinationSection -is [string]) {$settings = @{id=$DestinationSection}}
        else   {Write-Warning -Message 'Could not process the page parameter' ; return}
        #if the group GUID in the path to self in the destination use that, otherwise look for a group ID
        if     ($DestinationSection.Self -and
                $DestinationSection.Self -match "groups/([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})/") {
                                                   $settings['groupId'] = $Matches[1]}
        elseif ($GroupID.id)                      {$settings['groupId'] = $GroupID.ID}
        elseif ($GroupID -is [string] )           {$settings['groupId'] = $GroupID }
        elseif ($GroupID) {Write-Warning -Message 'Could not process the page parameter' ; return}

        $webparams['body'] = ConvertTo-Json $settings
        Write-Debug $webparams['body']
        $Op = Invoke-GraphRequest @webparams
        if ($Wait -and $op -and $DestinationSection.self -match "^(http.*/onenote/).*$") {
            $op2 = $op
            while ($op2.status -ne 'completed'){
                Write-Progress -Activity "Copying OneNote" -Status $op2.status
                Start-Sleep -Seconds 2
                $op2 = Invoke-GraphRequest "$($Matches[1])operations/$($op.id)" -ExcludeProperty @('@odata.context','@odata.type') -AsType ([MicrosoftGraphOnenoteOperation])
            }
            $op2
        }
        else {$op}
    }
}

<#
see https://docs.microsoft.com/en-us/graph/api/section-copytonotebook?view=graph-rest-1.0&tabs=http
Currenly /groups/{id}/onenote/sections/{id}/copyToNotebook gives a 404 error
function Copy-GraphOneNoteSection {
 
    param (
        #The section to be copied.
        [Parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true)]
         [ArgumentCompleter([OneNoteSectionCompleter])]
        $Section,
        #The section the it will be copied to
        [Parameter(Position=1,Mandatory=$true)]
        $DestinationNotebook,
 
        #The name of the copy. Defaults to the name of the existing item.
        $NewName,
 
        #The group which owns the notebook if required
        $GroupID,
        [switch]$Wait
    )
    process {
        $webparams =@{
            'Method' = 'POST'
            'ContentType' = 'application/json'
            'ExcludeProperty' = @('@odata.context','@odata.type')
            'AsType' = ([MicrosoftGraphOnenoteOperation])
 
        }
        if ($Section.self) {$webparams['uri']="$($Section.self)/copyToNotebook"}
        elseif ($Section -is [string]) {$webparams['uri']="$Section/copyToSection"}
        else {Write-Warning -Message 'Could not process the page parameter' ; return}
 
        if ($DestinationNotebook.id) {$settings = @{id=$DestinationNotebook.id }}
        elseif ($DestinationNotebook -is [string]) {$settings = @{id=$DestinationNotebook}}
        else {Write-Warning -Message 'Could not process the page parameter' ; return}
        #if the group GUID in the path to self in the destination use that, otherwise look for a group ID
        if ($DestinationNotebook.Self -and
                $DestinationNotebook.Self -match "groups/([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})/") {
                                                    $settings['groupId'] = $Matches[1]}
        elseif ($GroupID.id) {$settings['groupId'] = $GroupID.ID}
        elseif ($GroupID -is [string] ) {$settings['groupId'] = $GroupID }
        elseif ($GroupID) {Write-Warning -Message 'Could not process the page parameter' ; return}
 
        if ($NewName) {$settings['renameAs'] = $Matches[1]}
 
        $webparams['body'] = ConvertTo-Json $settings
        Write-Debug $webparams['body']
 
        $Op = Invoke-GraphRequest @webparams
        if ($Wait -and $op -and $DestinationSection.self -match "^(http.*/onenote/).*$") {
            $op2 = $op
            while ($op2.status -ne 'completed'){
                Write-Progress -Activity "Copying OneNote" -Status $op2.status
                Start-Sleep -Seconds 2
                $op2 = Invoke-GraphRequest "$($Matches[1])operations/$($op.id)" -ExcludeProperty @('@odata.context','@odata.type') -AsType ([MicrosoftGraphOnenoteOperation])
            }
            $op2
        }
        else {$op}
    }
}
#>

function Add-GraphOneNotePage    {
    <#
      .synopsis
        Adds a page (in HTML format) to an existing OneNote Section
      .description
        This posts to https://graph.microsoft.com/v1.0
            /users/{id}/onenote/sections/{id}/pages
        or /groups/{id}/onenote/sections/{id}/pages
        or /sites/{id}/onenote/sections/{id}/pages
        which requires consent to use the Notes.Create or Notes.ReadWrite scope or better.
        To recognise the title the page needs to be in HTML with a head tag like this
        <html>
            <head>
                <title>A page</title>
                <meta name="created" content="2015-07-22T09:00:00-08:00" />
            </head>
            <body>
                <p>Here's Some text</p>
            </body>
        </html>
      .Example
        >Add-GraphOneNotePage -Section $section -HTMLPage '<html><head><title>Test Page</Title></head><body><p>Sample Paragraph</p></body></html>'
        With $Section already defined this adds a simple page, with a title and a short body.
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    [Alias('Add-GraphNoteBookPage')]
    param   (
        #The section either as a URL or or as section object, which contains a self URL or a pages URL this can be set by Set-GraphOneNoteHome
        [Parameter(Mandatory=$true)]
        $Section ,
        #The content of the page formatted as HTML
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        $HTMLPage,
        #By default this is "text/html" - but if the content is multipart use "multipart/form-data; boundary={MARKER}"
        $ContentType = 'text/html',
        #If specified, the command will run without asking for confirmation; this is the default unless Confirm Preference has been set
        [switch]$Force,
        #Normally the page is added 'silently'. If passthru is specified, an object describing the new page will be returned.
        [Alias('PT')]
        [switch]$PassThru
    )

    if     ($Section.pagesURL)            {$uri = $Section.PagesUrl}
    elseif ($Section.self)                {$uri = $Section.Self + '/pages'}
    elseif ($Section -isnot [String])     {Write-Warning -Message 'Could not process the Section parameter'; Return }
    elseif ($Section -notmatch "/pages$") {$uri = $Section + '/pages'}
    else                                  {$uri = $Section}
    $webParams = @{'Method'          = 'Post'
                   'ContentType'     = $ContentType
                   'Body'            = $HTMLPage
                   'uri'             = $URI
                   'ExcludeProperty' = '@odata.context'
                   'AsType'          = ([MicrosoftGraphOnenotePage])
    }

    if ($Force -or $PSCmdlet.ShouldProcess($Section.DisplayName,'Add page to OneNote Section')) {
        $result =  Invoke-GraphRequest @webParams
        If ($PassThru) {
            if ($Section -is [MicrosoftGraphOnenoteSection]) {$result.ParentSection = $section}
            if ($section.parentnotebook.DisplayName)  {$result.parentNoteBook = $section.parentNotebook}
            return $result
        }
    }
}

function Add-FileToGraphOneNote  {
    <#
      .Synopsis
        Adds a file to a new OneNote page
      .DESCRIPTION
        Adds a file to a new one page. If the file is an image, the it will be rendered on the page
        Other files will be embedded. OneNote can render some types (e.g. PDF)
        This builds very simple HTML, which can be updated later.
        For more sophistaced pages use Add-GraphOneNotePage - with -HTMLPage as a byte array and
        specify a contentType of "multipart/form-data; boundary={MARKER}"
      .INPUTS
        A file to be sent to OneNote
      .example
        >
        >Add-FileToGraphOneNote -Path .\Modules\MsftGraph\Examples\upload.jpg -Title "Demo" -Section $notebook.sections[0] `
                  -PreContent "<h1>QR Code for the GIT repo</h1>" -PostContent "<b>Share and Enjoy</b>" -PassThru
 
        $Notebook holds a notebook object with one or more section(s). The command adds a page in the first section,
        titles it "Demo", and puts upload.jpg on it with formatted text before and after the image.
      .link
        Add-GraphOneNotePage.
    #>

    [CmdletBinding(SupportsShouldProcess=$true)]
    [Alias('Add-FileToGraphNoteBook')]
    param   (
        #The file to upload to OneNote
        [Parameter(ValueFromPipeline=$true,Mandatory=$true)]
        $Path ,
        #Title for the page. If not specified the file name will be used.
        [String]$Title,
        #Section to post to - this can be set by Set-GraphOneNoteHome
        $Section,
        #Specifies text to add before the embedded object. By default, there is no text in that position.
        [ValidateNotNullOrEmpty()][string[]]$PreContent,
        #Specifies text to add after the embedded object. By default, there is no text in that position.
        [ValidateNotNullOrEmpty()][string[]]$PostContent,
        #A recognized mime type for the embedded file. on Windows the command will try to determine this from the file extension.
        [string]$MimeType,
        #Normally the page containing the file is added 'silently'. If passthru is specified, an object describing the new page will be returned.
        [Alias('PT')]
        [switch]$PassThru,
        #If specified the command will not pause for conformation, this is the default unless $ConfirmPreference is modified,
        [switch]$Force
    )
    process {
        #region set the URI - based on $Section - and other parameters used to send the page
        $webParams = @{ 'Method'          = 'Post'
                        'ContentType'     = 'multipart/form-data; boundary=MyAppPartBoundary'
                        'ExcludeProperty' = '@odata.context'
                        'AsType'          = ([MicrosoftGraphOnenotePage])
        }
        #if we got a section object use its pages URL, otherwise if we got a string without pages on the end, add pages, otherwise use section as is
        if     (-not $section )                  {throw [ParameterBindingException]::new("Section parameter is required")}
        elseif ($Section.pagesURL)               {$webParams['uri'] =  $Section.pagesURL}
        elseif ($Section -is [string] -and
                $Section -notmatch "/pages$")    {$webParams['uri'] = ($Section -replace '/$','')  + "/pages"}
        elseif ($Section -is [string])           {$webParams['uri'] =  $Section}
        else   {Write-Warning -Message 'Could not process the -Section paramater' ; return}
        if     ($webParams['uri']-notmatch "/onenote/sections/") {Write-Warning -Message "That does not appear to be a valid section" ; return}
        #endregion
        #region read file into a data block in HTML
        $i = Get-Item -Path $Path
        if ($i.count -ne 1) {Write-Warning "The path must match exactly one file. $path matches $($i.count)." ; return  }

        if ([System.Environment]::OSVersion -match "win" -and -not $MimeType) {
            $MimeType          =  (Get-ItemProperty -Path (Join-Path "HKLM:\SOFTWARE\Classes\" $I.Extension)  -Name "content type")."Content type"
        }
        if (-not $MimeType) {Write-Warning "The Mime type could not be determined automatically. Please specify the mimetype for '$path' with -MimeType." ; return  }

        [String]$filename      =      $i.Name
        [byte[]]$array         = [System.IO.File]::ReadAllBytes($i.fullName)

        if ($MimeType -match "image") {
                $imgTag         = '<img src="name:FileBlock" width="500"/>'
        }
        else      {
                $imgTag        = '<img data-render-src="name:FileBlock" width="1024"/>'
                $objectTag     = '<p align="center"><object data-attachment="{1}" data="name:FileBlock" type="{0}" /></p>' -f $mimetype, $filename
        }
        if ($Title) {$tTag     = [System.Web.HttpUtility]::HtmlEncode($Title)}
        else        {$tTag     =  $i.Name}
        [byte[]]$myhtml        = ([byte[]][char[]]( @"
--MyAppPartBoundary
Content-Disposition:form-data; name="Presentation"
Content-type:text/html
 
<!DOCTYPE html>
<html>
 <head><title>$tTag</title></head>
 <body>$PreContent<p>$imgTag</p>$objectTag $PostContent</body>
</html>
 
--MyAppPartBoundary
Content-Disposition:form-data; name="FileBlock"
Content-type:$mimetype
`r`n
"@
 ))  + $array + ([byte[]][char[]]"`r`n--MyAppPartBoundary--`r`n")
        #endregion
        #region Send it - return the new page if -passthru was given
        if ($Force -or $PSCmdlet.ShouldProcess($tTag,'Add page to OneNote Section')) {
            $result =  Invoke-GraphRequest @webParams -Body $myhtml
            If ($PassThru) {
                if ($Section -is [MicrosoftGraphOnenoteSection]) {$result.ParentSection = $section}
                if ($section.parentnotebook.DisplayName)  {$result.parentNoteBook = $section.parentNotebook}
                return $result
            }
        }
        #endregion
    }
}

function Update-GraphOneNotePage {
    <#
        .Synopsis
            Update a OneNote page
        .Description
            This command makes PATCH requests to https://graph.microsoft.com/v1.0
                /users/{id}/onenote/sections/{id}/pages/{id}/content
            or /groups/{id}/onenote/sections/{id}/pages/{id}/content
            or /sites/{id}/onenote/sections/{id}/pages/{id}/content
            which requires consent to use the Notes.ReadWrite scope or better.
            To understand the use of Target, action & Postion and what needs to
            be in content for different scenarios, read the MSFT page at the link ...
        .link
            https://docs.microsoft.com/en-gb/graph/onenote-update-page
    #>

    [cmdletbinding(SupportsShouldProcess=$true)]
    [Alias('Update-GraphNoteBookPage')]
    param   (
        #A graph URI pointing to the page, or a page object where the .self property is a graph URI...
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        $Page,
        #The action to perform on the target element.
        [ValidateSet('replace', 'append', 'delete', 'insert', 'prepend')]
        [String]$Action =  'append' ,
        # A string of well-formed HTML to add to the page, and any image or file binary data.
        [Parameter(Mandatory=$true)]
        [String]$Content,
        #The location to add the supplied content, relative to the target element.
        [ValidateSet('after','before')]
        [String]$Position,
        #The element to update. Must be the #<data-id> or the generated <id> of the element, or the body or title keyword.
        [String]
        $Target = 'body',
        #If specified, the page is updated without prompting.
        [switch]$Force
    )
    process {
         #If the content contains binary data, the request must be sent using the multipart/form-data content type with a "Commands" part.
        if     ($Page.self)          {$uri = $Page.self}
        elseif ($Page -is [String])  {$uri = $Page}
        Write-Progress -Activity 'Updating Page' -Status 'Checking exsiting page'
        try {
            $result = Invoke-GraphRequest -Uri  $URI
        }
        catch {
            Write-Progress -Activity 'Updating Page' -Completed
            if ($_.exception.response.statuscode.value__ -eq 404) {
                Write-Warning "Could not find the page" ; return
            }
            else {throw $_ ; return}
        }
        $settings = @{
            'target'   = $Target   #body by default
            'action'   = $Action;  #Append by default
            'content'  = $Content;
        }

        if ($Position) {$settings['position'] = $Position}

        $json = ConvertTo-Json @($settings)
        Write-Debug $json
        if ($Force -or $PSCmdlet.ShouldProcess($result.title, 'Update Onenote Page')) {
            Write-Progress -Activity 'Updating Page'  -Status 'Applying changes'
            $result = Invoke-GraphRequest -Method Patch -Uri  "$URI/content"  -ContentType "application/json" -Body $json
            Write-Progress -Activity 'Updating Page' -Completed
        }
    }
}

function Remove-GraphOneNotePage {
    <#
      .Synopsis
        Removes a OneNote page
      .Description
           This command makes DELETE requests to https://graph.microsoft.com/v1.0
                /users/{id}/onenote/sections/{id}/pages/{id}
            or /groups/{id}/onenote/sections/{id}/pages/{id}
            or /sites/{id}/onenote/sections/{id}/pages/{id}
            which requires consent to use the Notes.ReadWrite scope or better.
      .Example
         >Get-GraphUser -Teams -Name Consultants | Get-GraphTeam -Notebooks |
            Get-GraphOneNoteBook -Sections -Name General | Get-GraphOneNoteSection -Pages -Name process | Remove-GraphOneNotePage
        finds a team named "consultants" which has the current user as a member, finds its notebook, finds a section named General
        within this sectioned finds page names that begin "process..." and removes them
    #>

    [cmdletbinding(ConfirmImpact='High',SupportsShouldProcess=$true)]
    [Alias('Remove-GraphNoteBookPage')]
    param   (
        #A graph URI pointing to the page, or a page object where the .self property is a graph URI...
        [Parameter(Mandatory=$true, ValueFromPipeline=$true)]
        $Page,
        #If specified, the page is deleted without prompting.
        [switch]$Force
    )
    process {
        if     ($Page.self)         {$uri = $Page.self}
        elseif ($Page -is [string]) {$uri = $Page}
        else   {Write-Warning -Message 'Could not process the Page parameter' ; return}
        Write-Progress -Activity 'Deleting OneNote page(s)' -Status 'Checking page'
        try {
            $result = Invoke-GraphRequest -Uri  $uri
        }
        catch {
            Write-Progress -Activity 'Deleting OneNote page(s)' -Completed
            if ($_.exception.response.statuscode.value__ -eq 404) {
                Write-Warning "Could not find the page, it may have been deleted already." ; return
            }
            else {throw $_ ; return}
        }
        if ($Force -or $PSCmdlet.ShouldProcess($result.title, 'Delete Onenote Page')) {
            Write-Progress -Activity 'Deleting OneNote page(s)' -Status 'Deleting' -CurrentOperation $result.title
            $result = Invoke-GraphRequest  -Uri  $uri -Method  Delete
        }
        Write-Progress -Activity 'Deleting OneNote page(s)' -Completed
    }
}

function Out-GraphOneNote        {
    <#
      .Synopsis
        Output to a new OneNote page
      .INPUTS
        You can pipe any .NET object to Out-GraphOneNote
     .EXAMPLE
        Generates a page
      .EXAMPLE
        start ( Get-process | Out-GraphOneNote -Title "Processes @ $(get-date)" -property Name,Handles,NPM,PM,VM,WS -passthru ).links.oneNoteWebUrl.href
        Generates a page in the default section (using the environment variable DefaultOneNoteSection) and opens it in a web browser.
    #>

    [CmdletBinding(DefaultParameterSetName='Page')]
    [Alias('Out-GraphNoteBook')]
    param   (
        #Specifies the objects to be represented in HTML.
        [parameter(ValueFromPipeline=$true)]
        [psobject]$InputObject,
        #Includes the specified properties of the objects in the output
        [Parameter(Position=0)]
        [String[]]$Property = @('*'),
        #The section where the content will be created: to this can be set by Set-GraphOneNoteHome
        $Section ,
        [Parameter(ParameterSetName='Page', Position=4)]
        #Specifies the text to add after the opening <BODY> tag. By default, there is no text in that position.
        [string[]]$Body,
        #Specifies the content of the <HEAD> tag. The default is "<title>HTML TABLE</title>". If you use the Head parameter, the Title parameter is ignored.
        [Parameter(ParameterSetName='Page', Position=2)]
        [string[]]$Head,
        #Specifies a title for the Page.
        [Parameter(ParameterSetName='Page', Position=3)]
        [ValidateNotNullOrEmpty()][string]$Title,
        #Determines whether the object is formatted as a table or a list. Valid values are TABLE and LIST. The default value is TABLE.
        [ValidateSet('Table','List')][string]$As = 'Table',
        #Generates only an HTML table. The HTML, HEAD, TITLE, and BODY tags are omitted.
        [Parameter(ParameterSetName='Fragment')]
        [switch]$Fragment,
        [String[]]$ExcludeProperty,
        # Specifies text to add before the opening <TABLE> tag. By default, there is no text in that position.
        [ValidateNotNullOrEmpty()][string[]]$PreContent,
        #Specifies text to add after the closing </TABLE> tag. By default, there is no text in that position.
        [ValidateNotNullOrEmpty()][string[]]$PostContent,
        #Normally the page is added 'silently'. If passthru is specified, an object describing the new page will be returned.
        [Alias('PT')]
        [switch]$PassThru,
        #If Specified opens the newly created page
        [switch]$Show
    )
    #collect whatever comes in as Input object and process in the end block
    begin   { $stuff = @() }
    process { $Stuff = $Stuff + $InputObject}
    end     {
        #region gather the parameters for the API call - built URI from $section
         #if we got a section object use its pages URL, otherwise if we got a string without pages on the end, add pages, otherwise use section as is
        if     (-not $section )                  {throw [ParameterBindingException]::new('Section parameter is required')}
        $webParams = @{ 'Method'          = 'Post'
                        'ContentType'     = 'text/html'
                        'ExcludeProperty' = '@odata.context'
                        'AsType'          = ([MicrosoftGraphOnenotePage])
        }
        if ($Section.pagesURL) {
                $webParams['uri'] = $Section.pagesURL
        }
        elseif ($Section -is [string] -and $Section -notmatch '/pages$') {
                $webParams['uri'] = ($Section -replace '/$','')  + '/pages'
        }
        else   {$webParams['uri'] = $Section}
        if     ($webParams['uri']-notmatch '/onenote/sections/') {Write-Warning -Message 'That does not appear to be a valid section' ; return}
        #end region
        #region generate the HTML - filtering the input properties as needed
        if ($PSBoundParameters['Property'] -or $PSBoundParameters['ExcludeProperty']) {
            $Stuff = $stuff | Select-Object -Property $Property -ExcludeProperty $ExcludeProperty
            $null = $PSBoundParameters.Remove('Property'), $PSBoundParameters.Remove('ExcludeProperty')
        }

        $null = $PSBoundParameters.Remove('Section'),$PSBoundParameters.Remove('InputObject'), $PSBoundParameters.Remove('PassThru'), $PSBoundParameters.Remove('Show')

        if (-not $Title)    {
            $PSBoundParameters.Add('Title', ( $MyInvocation.Line + ' - ' +  (Get-Date).ToString() ))
        }
        $webParams['body'] = $Stuff | ConvertTo-Html  @PSBoundParameters
        #end region

        #Make the call, returning the URL of the new page.
        $result = Invoke-GraphRequest @webParams
        if ($Show)     { Start-Process $result.Links.OneNoteWebUrl.Href}
        if ($PassThru) {
                if ($Section -is [MicrosoftGraphOnenoteSection]) {$result.ParentSection = $Section}
                if ($Section.parentnotebook.DisplayName)  {$result.parentNoteBook = $Section.parentNotebook}
                return $result
        }
    }
}