OneNote.ps1
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-Graphuser -teams | select -First 1 | Get-GraphTeam -Notebooks | select -first 1 | Get-GraphOneNoteBook -Sections | ft DisplayName, @{n="Notebook";e={$_.parentNotebook.DisplayName}} Gets the first team for a user; gets the first notebook for that team and gets its sections, which are formatted as a table. .Example >Get-GraphOneNoteBook -name general Gets a users workbook with the name "General", "GENERAL", "general" - the search is case insensitive. .Example >Get-GraphOneNoteBook -Sections -name Powershell Finds a PowerShell secion in any of the users workbooks. Again the search is casse insensitive #> [cmdletbinding(DefaultParameterSetName="None")] param ( #A graph URI pointing to the notebook, or a notebook object where the .self property is a graph URI... [Parameter(ValueFromPipeline=$true)] $Notebook , #If specified returns the sections of the notebook. [switch]$Sections, #if specified filters the returned objects by to those with names begining with ... [string]$Name ) begin { Connect-MSGraph $webParams = @{Method = "Get" Headers = $Script:DefaultHeader } } process { if ($Notebook.self) {$Notebook=$Notebook.self} if ($Name) {$Name = '?$filter=startswith(tolower(displayname),''{0}'')' -f ($Name.ToLower() -replace '\*$','') } #Combinations of params. Just notebook, Just Sections or both (with or without name), neither if ($notebook -and -not $sections){ Write-Progress "Getting Notebook Information" $n = (Invoke-RestMethod @webParams -Uri ("$Notebook`?`$expand=Sections" + ($Name -replace "^\?","&"))) $n.pstypeNames.Add("GraphOneNoteBook") #Section fetched this way won't have parentNotebook, so make sure it is available when needed $bookobj =new-object -TypeName psobject -Property @{'id'=$n.id; 'displayname'=$n.displayName; 'Self'=$n.self} foreach ($s in $n.sections) { $s.pstypeNames.add("GraphOneNoteSection") Add-Member -InputObject $s -MemberType NoteProperty -Name ParentNotebook -Value $bookobj } Write-Progress "Getting Notebook Information" -Completed return $n } elseif ($sections) { if ($notebook) {$results = Invoke-RestMethod @webParams -Uri ("$Notebook/sections" + $Name) } else {$results = Invoke-RestMethod @webParams -Uri ('https://graph.microsoft.com/v1.0/me/onenote/Sections' + $Name) } #Section fetched this way have parentNotebook $sectionList = $results.value foreach ($s in $sectionList) { $s.pstypeNames.add("GraphOneNoteSection") } return $sectionList } else { $n = (Invoke-RestMethod @webParams -Uri ('https://graph.microsoft.com/v1.0/me/onenote/notebooks?`$expand=Sections' + ($Name -replace '^\?','&') )) if($n.value) { foreach ($item in $n.value) { $item.pstypeNames.Add('GraphOneNoteBook') #Section fetched this way won't have parentNotebook, so make sure it is available when needed $bookobj =new-object -TypeName psobject -Property @{'id'=$item.id; 'displayname'=$item.displayName; 'Self'=$item.self} foreach ($s in $item.sections) { $s.pstypeNames.add('GraphOneNoteSection') Add-Member -InputObject $s -MemberType NoteProperty -Name ParentNotebook -Value $bookobj } } return $n.value } elseif ($n.self) { $n.pstypeNames.Add('GraphOneNoteBook') #Section fetched this way won't have parentNotebook, so make sure it is available when needed $bookobj =new-object -TypeName psobject -Property @{'id'=$n.id; 'displayname'=$n.displayName; 'Self'=$n.self} foreach ($s in $n.sections) { $s.pstypeNames.add('GraphOneNoteSection') Add-Member -InputObject $s -MemberType NoteProperty -Name ParentNotebook -Value $bookobj } return $n } } } } 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 -ByName accounts -Notebooks >Get-GraphOneNoteSection -Pages $notebook.sections[0] The first line gets the Notebooks object for the Accounts team. This has a sections collection. The second line gets the pages in the first 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()] param ( #A graph URI pointing to the section, or a section object where the .self property is a graph URI... [Parameter(Mandatory=$true, ValueFromPipeline=$true,ParameterSetName='Sections',Position=0)] $Section , [Parameter(ParameterSetName='Notebook')] $Notebook , #If Specified returns the pages in the section. [Parameter(ParameterSetName='Sections',Position=1)] [switch]$Pages, #If specified filters pages or Sections to those with names beginning ... [string]$Name ) begin { Connect-MSGraph $webParams = @{Method = "Get" Headers = $Script:DefaultHeader } } process { if ($Notebook) { #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} elseif ($Notebook.self) {$uri = $Notebook.self +"/sections"} elseif ($Notebook -is [string]) {$uri = $Notebook +"/sections"} else {Write-warning -Message 'Could not process the notebook parameter provided'} if ($Name) {$uri += ('?$filter=startswith(tolower(displayname),''{0}'')' -f ($Name.ToLower() -replace '\*$','')) } $results = Invoke-RestMethod @webParams -Uri $uri $sectionList = $results.value foreach ($s in $sectionList) { $s.pstypeNames.add("GraphOneNoteSection") } return $sectionList } if ($Section.self) {$uri = $Section.self} elseif ($Section -is [string]) {$uri = $Section} else {Write-Warning 'Can not process the Section Parameter' ; Return } if ($Name -or $Pages) { if ($Name) {$uri = "$uri/Pages?`$filter=startswith(tolower(title),'$Name')" } else {$uri = "$uri/Pages"} $p = (Invoke-RestMethod @webParams -Uri $uri).value foreach ($page in $p) {$page.pstypeNames.add("GraphOneNotePage")} return $p } else { $result = Invoke-RestMethod @webParams -Uri $uri $result.pstypeNames.add("GraphOneNoteSection") return $result } } } 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)] param ( #A graph URI pointing to the notebook, or a notebook object [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 { Connect-MSGraph $webParams = @{'Method' = 'Post' 'Headers' = $Script:DefaultHeader 'ContentType' = 'application/json' } if ($Notebook.sectionsUrl) {$uri = $Notebook.sectionsUrl} elseif ($Notebook.self) {$uri = $Notebook.self + "/sections"} elseif ($Notebook -isnot [String]) {Write-Warning -Message 'Could not process the Notebook parameter'; Return } elseif ($notebook -notmatch "/sections$") {$uri = $Notebook + "/sections"} else {$uri = $Notebook } } process { $json = ConvertTo-Json @{"displayName" = $sectionName} Write-Debug $json if ($Force -or $PSCmdlet.ShouldProcess($SectionName,"Add section to Notebook $($Notebook.displayname)")) { $result = Invoke-RestMethod @webParams -Uri $uri -Body $json $result.pstypenames.add('GraphOneNoteSection') return $result } } } 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()] 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 returns the contents of the page. Ignored if ContentWithIDs is specified [switch]$Content, #If specified returs the contents with guids for each section where content can be inserted. [switch]$ContentWithIDs ) begin { Connect-MSGraph $webParams = @{'Method' = 'Get'; 'Headers' = $Script:DefaultHeader } } process { if ($Page.self) {$uri=$Page.self} elseif ($page-is [string]) {$uri=$Page} else {Write-Warning -Message 'Could not process the page parameter' ; return} #Normally we want Invoke-RestMethod, but here we want the unprocessed content. if ($ContentWithIDs) {(Invoke-WebRequest @webParams -Uri "$uri/Content?includeIDs=true").Content} elseif ($Content) {(Invoke-WebRequest @webParams -Uri "$uri/Content").Content} #should return the outer xml property as this is HTML in an XML document. Check what else it comes back as else { $result = Invoke-RestMethod @webParams -Uri $uri $result.pstypeNames.add("GraphOneNotePage") return $result } } } 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)] param ( #The section either as a URL or or as section object, which contrains a self URL or a pages URL [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 ) Connect-MSGraph 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' 'Headers' = $Script:DefaultHeader 'ContentType' = $ContentType 'Body' = $HTMLPage } if ($Force -or $PSCmdlet.ShouldProcess($Section.DisplayName,'Add page to OneNote Section')) { $result = Invoke-WebRequest @webParams -uri $uri Write-Verbose -Message "Return status was $($result.StatusCode)/$($result.StatusDescription)" If ($PassThru) { $p = ConvertFrom-Json $result.Content $p.pstypeNames.add('GraphOneNotePage') Add-Member -InputObject $p -MemberType NoteProperty -Name 'ParentSection' -Value $Section return $p } } } 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)] 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. $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, #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 ) begin { $webParams = @{ 'Method' = 'Post' 'Headers' = $Script:DefaultHeader 'ContentType' = 'multipart/form-data; boundary=MyAppPartBoundary' } } process { #If section wasn't passed but we have it in an enviroment variable use that if (-not $Section -and $env:DefaultOneNoteSection) {$Section = $env:DefaultOneNoteSection} elseif (-not $section ) {throw "Section parameter is required"} #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 ($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} #check we have a valid URI for posting to if ($webParams['uri']-notmatch "/onenote/sections/") {Write-Warning -Message "That does not appear to be a valid section" ; return} #region read file $i = Get-Item -Path $Path if ($i.count -ne 1) {Write-Warning "The path must be exactly one file. $path matches $($i.count)." ; return } #Not sure where this came from and why I don't just use [byte[]]$array = [System.IO.File]::ReadAllBytes($i.fullName) [String]$filename = $i.Name [byte[]]$array = ,0 * $i.length $stream = $i.OpenRead() [void]$stream.Read($array, 0, $i.Length) $stream.Close() #endregion #region Prepare Data to send $mimetype = (Get-ItemProperty -Path (Join-Path "HKLM:\SOFTWARE\Classes\" $I.Extension) -Name "content type")."Content type" if ($mimetype -match "image") { $imgTag = '<img src="name:MyAppFileBlockName" width="500"/>'} else {$imgTag = '<img data-render-src="name:MyAppFileBlockName" width="1024"/>' $objectTag = '<p align="center"><object data-attachment="{1}" data="name:MyAppFileBlockName" 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="MyAppFileBlockName" Content-type:$mimetype `r`n "@ )) + $array + ([byte[]][char[]]"`r`n--MyAppPartBoundary--`r`n") #endregion #Send it if ($Force -or $PSCmdlet.ShouldProcess($tTag,'Add page to OneNote Section')) { $result = Invoke-WebRequest @webParams -Body $myhtml Write-Verbose -Message "Return status was $($result.StatusCode)/$($result.StatusDescription)" If ($PassThru) { $p = ConvertFrom-Json $result.Content $p.pstypeNames.add('GraphOneNotePage') Add-Member -InputObject $p -MemberType NoteProperty -Name 'ParentSection' -Value $Section return $p } } } } 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)] 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 ) begin { Connect-MSGraph } 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} try { Write-Progress -Activity 'Updating Page' -Status 'Checking exsiting page' $result = Invoke-RestMethod -Headers $Script:DefaultHeader -Uri $URI -Method Get } 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-WebRequest -Method Patch -Uri "$URI/content" -Headers $Script:DefaultHeader -ContentType "application/json" -Body $json Write-Progress -Activity 'Updating Page' -Completed Write-Verbose -Message "Update response was $($result.statuscode)/$($result.statusDescription)" } } } 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)] 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 ) begin { Connect-MSGraph } process { if ($Page.self) {$uri = $Page.self} elseif ($Page -is [string]) {$uri = $Page} else {Write-Warning -Message 'Could not process the Page parameter' ; return} try { Write-Progress -Activity 'Deleting OneNote page(s)' -Status 'Checking page' $result = Invoke-RestMethod -Headers $Script:DefaultHeader -Uri $uri -Method Get } 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-WebRequest -Headers $Script:DefaultHeader -Uri $uri -Method Delete Write-Verbose -Message "Delete response was $($result.statuscode) $($result.statusDescription)" } 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-OneNoteLive .EXAMPLE Generates a page .EXAMPLE start ( Get-process | Out-OneNoteLive -Title "Processes @ $(get-date)" -property Name,Handles,NPM,PM,VM,WS ) Generates a page and opens it #> [CmdletBinding(DefaultParameterSetName='Page')] 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)] [System.Object[]] $Property, #The section to the content to this can be set in an environment variable DefaultOneNoteSection. $Section, [Parameter(ParameterSetName='Page', Position=3)] #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=1)] [string[]] $Head, #Specifies a title for the Page. [Parameter(ParameterSetName='Page', Position=2)] [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')] [ValidateNotNullOrEmpty()][switch]$Fragment, # 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 ) begin { $stuff = @() } process { $Stuff = $Stuff + $InputObject} end { Connect-MSGraph $webParams = @{ Method = "Post" Headers = $Script:DefaultHeader ContentType ="text/html" } #If section wasn't passed but we have it in an enviroment variable use that if(-not $Section -and $env:DefaultOneNoteSection) {$Section = $env:DefaultOneNoteSection} elseif(-not $section ) {throw "Section parameter is required"} #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 ($Section.pagesURL) {$webParams['uri'] = $Section.pagesURL} elseif ($Section -is [string] -and $Section -notmatch "/pages$") {$webParams['uri'] = ($Section -replace '/$','') + "/pages"} else {$webParams['uri'] = $Section} #check we have a valid URI for posting to if ($webParams['uri']-notmatch "/onenote/sections/") {Write-Warning -Message "That does not appear to be a valid section" ; return} #Generate HTML [void]$PSBoundParameters.Remove("Section") [void]$PSBoundParameters.Remove("InputObject") if (-not $Title) {$PSBoundParameters.Add("Title", ( $MyInvocation.Line + " - " + (Get-Date))) } $webParams['body'] = $Stuff | ConvertTo-Html @PSBoundParameters #And post it, returning the URL of the page. (Invoke-RestMethod @webParams ).links.onenoteWebUrl.href } } function Add-GraphOneNoteTab { <# .Synopsis Adds a tab in a Teams channel for a OneNote section or Notebook .Description This posts to https://graph.microsoft.com/v1.0/teams/{id}/channels/{id}/tabs which requires consent to use the Group.ReadWrite.All scope. The Notebook Parameter has an alias of 'Section' and will accept either a OneNote Notebook object (or its 'Self' URI - which requires the tab name to be set explicitly) or a Section object. If the notebook is specified it opens at the first section. .Example > > $section = Get-GraphTeam -ByName accounts -Notebooks | Select-Object -ExpandProperty sections | where displayname -like "FY-19*" > $channel = Get-GraphTeam -ByName accounts -Channels -ChannelName 'year-end' > Add-GraphOneNoteTab $section $channel -TabLabel "FY-19 Notes" The first command gets the Notebook for the Accounts team and finds the "FY-19 Year End" section The second command gets the channels for the same team and finds the "Year end" channel The Third command creates a tab in the channel named 'FY-19 Notes' which opens the team notebook at its 'FY-19 Year End' section. #> [CmdletBinding(SupportsShouldProcess)] param( #The Notebook or Section to associate with the tab [Parameter(Mandatory=$true,Position=0)] [Alias('Section')] $Notebook, #An ID or Channel object which may contain the team ID; the tab will be created in this channel [Parameter(Mandatory=$true, Position=1)] $Channel, #A team ID, or a team object if the team can't be found from the the channel $Team, #The label for the tab, if left blank the name of the Notebook or Section will be sued $TabLabel, #Normally the tab is added 'silently'. If passthru is specified, an object describing the new tab will be returned. [Alias('PT')] [switch]$PassThru, #If Specified the tab will be added without pausing for confirmation, this is the default unless $ConfirmPreference has been set. $Force ) Connect-MSGraph if (-not $Script:WorkOrSchool) {Write-Warning -Message "This command only works when you are logged in with a work or school account." ; return } if ($Channel.Team) {$Team = $Channel.Team } elseif ($Team.id) {$Team = $Team.id} elseif ($team -isnot [string]) {Write-Warning 'Unable to determine the team, please specify it explicitly'; return} if ($Channel.id) { $Channel = $Channel.id } elseif ($Channel-isnot [string]) {Write-Warning 'Unable to determine the channel'; return} if (-not $TabLabel -and $notebook.displayName) {$TabLabel = $Notebook.displayName} elseif (-not $TabLabel) {Write-warning 'Unable to determin a name for the tab, please specify one explicitly'; return} $webparams = @{'Method' = 'Post'; 'Uri' = "https://graph.microsoft.com/beta/teams/$team/channels/$channel/tabs" ; 'Headers' = $Script:DefaultHeader; 'ContentType' = 'application/json' } #This bit had to be reverse engineered, from a beta version of the API, so if it works past next week, be happy. #If the "Notebook" object is actually a section, and it was fetched by one of the module commands (get-GraphTeam -notebook, or get-graphNotebook -section) #then $Notebook it will have a a parentNotebook ID. This IF..Else is to make sure we have the real notebook ID, and catch a sectionID if there is one. if ($Notebook.parentNotebook.id) { $ParamsPt2 = '¬ebookSource=PickSection§ionId='+ $Notebook.id $NotebookID = $Notebook.parentNotebook.id } else { $ParamsPt2 = '¬ebookSource=New' $NotebookID = $Notebook.id } #if $Notebook is a section its url will end ?wd=(something). We need to split this off the URL and re-use it. The () need to be unescapted too, if ($notebook.links.oneNoteWebUrl.href -match '\?(wd=.*$)') { $ParamsPt2 += '&' + ( $Matches[1] -replace '%28','(' -replace '%29',')' ) $OnenoteWebUrl = $notebook.links.oneNoteWebUrl.href -replace '\?wd=.*$', '' } else { $OnenoteWebUrl = $notebook.links.oneNoteWebUrl.href} #We need the teamsite URL for the team who owns this channel, and the URL to the the Notebook. Both need to be escaped. $OnenoteWebUrl = $OnenoteWebUrl -replace "%", "%25" -replace '/','%2F' -replace ':','%3A' $siteUrl = (Get-GraphTeam -Team $Team -Site).webUrl -replace "%", "%25" -replace '/','%2F' -replace ':','%3A' #Now we need to build up the mother and father of all URIs It contains the ID and URL for the notebook (not section). The Name, the teamsite. And Section specifics if applicable. $URIParams = "?entityid=%7BentityId%7D&subentityid=%7BsubEntityId%7D&auth_upn=%7Bupn%7D&ui={locale}&tenantId={tid}"+ "¬ebookSelfUrl=https%3A%2F%2Fwww.onenote.com%2Fapi%2Fv1.0%2FmyOrganization%2Fgroups%2F$Team%2Fnotes%2Fnotebooks%2F"+ $NotebookID + "&oneNoteWebUrl=" + $oneNoteWebUrl + "¬ebookName=" + [uri]::EscapeDataString( $notebook.displayName ) + "&siteUrl=" + $SiteUrl + $ParamsPt2 #Now we can create the JSON. Such information as there is can be found at https://docs.microsoft.com/en-us/graph/teams-configuring-builtin-tabs $json = ConvertTo-Json ([ordered]@{ 'TeamsAppId' = '0d820ecd-def2-4297-adad-78056cde7c78' 'name' = $TabLabel 'configuration' = [ordered]@{ 'entityId' = ((New-Guid).tostring() + "_" + $Notebook.ID) 'contentUrl' = "https://www.onenote.com/teams/TabContent" + $URIParams 'removeUrl' = "https://www.onenote.com/teams/TabRemove" + $URIParams 'websiteUrl' = "https://www.onenote.com/teams/TabRedirect?redirectUrl=$oneNoteWebUrl" }}) $json= $json -replace "\\u0026","&" Write-Debug $json if ($Force -or $PSCmdlet.ShouldProcess($TabLabel,"Add Tab")) { $result = Invoke-RestMethod @webParams -body $json if ($PassThru) { $result.pstypeNames.add('GraphTab') #Giving a type name formats things nicely, but need to set the name to be used when the tab is displayed Add-Member -InputObject $result -MemberType NoteProperty -Name teamsAppName -Value 'OneNote' return $result } } } |