Smartsheet.psm1
using namespace System.Collections.Generic <# Modulename: Smartsheet Description: Powershell module to interact with the SMartsheet API Object specific functions are in the ./public folder. #> #$BaseURI = "https://api.smartsheet.com/2.0" $Mimes = import-csv -Path "$PSScriptRoot/private/mimetypes.csv" $script:MimeTypes = [Dictionary[string, string]]::New() foreach ($mime in $mimes) { $script:MimeTypes.Add($mime.Extension, $mime.MIMEType) } # dot source the following files. . $PSScriptRoot/private/private.ps1 . $PSScriptRoot/public/images.ps1 . $PSScriptRoot/public/objects.ps1 . $PSScriptRoot/public/columns.ps1 . $PSScriptRoot/public/containers.ps1 . $PSScriptRoot/public/rows.ps1 . $PSScriptRoot/public/sheets.ps1 . $PSScriptRoot/public/shares.ps1 . $PSScriptRoot/public/attachments.ps1 . $PSScriptRoot/public/discussions.ps1 . $PSScriptRoot/public/search.ps1 . $PSScriptRoot/public/workspaces.ps1 # Setup Functions function Set-SmartsheetAPIKey () { [CmdletBinding(DefaultParameterSetName = 'default')] Param( [Parameter(Mandatory = $true)] [string]$APIKey, [switch]$Secure ) If ($Secure) { $Secretin = @{ APIKey = $APIKey } $Secret = $SecretIn | ConvertTo-Json Set-Secret -Name SmartSheet -Secret $Secret $objConfig = @{ APIKey = 'secure' } } else { $objConfig = @{ APIKey = $APIKey } } $configPath = "{0}/.smartsheet" -f $HOME if (-not (Test-Path -Path $configPath)) { New-Item -Path $configPath -ItemType:Directory | Out-Null } $objConfig | ConvertTo-Json | Out-File -FilePath "$configPath/config.json" -Force <# .SYNOPSIS Set the API key. .DESCRIPTION Creates a file in the user profile folder in the .smartsheet folder named config.json. This file contains the users Smartsheet API Token. .PARAMETER APIKey The Smartsheet API Access Token. .PARAMETER Secure This switch instructs the function to save the APIKey into a secure vault. This vaule is created and managed by the modules: Microsoft.PowerShell.SecretManagement Microsoft.PowerShell.SecretStore See the note below on how to setup a secret store. .NOTES To use a secret store to securely store you API key, you must install the required modules and configure the store and create a vaule. To install the modules: Install-Module Microsoft.PowerShell.SecretManagement Install-Module Microsoft.PowerShell.SecretStore Configure the secret store: You must configure your secret store and set the authenticatio, there are several parameters: -Authentication: This can ne either 'Password" or 'None' You should set a password for interactive sessions. For automation on a secure device you can set this to 'None' -Interaction: This can be set to 'Prompt' or 'None' If -Authentication is set to 'Password' This must be set to 'Prompt', id Authentication is set to 'None' this must ne set to 'None'. -PasswordTimeOut: The time in seconds befopre the password must be re-entered. The default is 600 seconds. -Password: The password to unlock the vault. If omitted you will be prompted for the password. Register the vault: You must register a vault to store your secrets. At this time the Secret Store does not support multiple vaules. While you "can" register multiple vaults the store will just be duplicated. This is a known issue with the SecretStore and may be corrected in the future. Only create 1 vault and register it as the default vault. Register-SecretVault -Name "MyVault" -DefaultVault -ModuleName Microsoft.Powershell.SecretStore #> } # End Setup Functions #Export Functions function Export-SmartSheet() { [CmdletBinding(DefaultParameterSetName = "default")] Param( [Parameter( Mandatory = $true, ValueFromPipeline = $true )] [psobject]$InputObject, [Parameter(Mandatory = $true)] [string]$SheetName, [Parameter(ParameterSetName = 'folder')] [Uint64]$FolderId, [Parameter(ParameterSetName = 'workspace')] [UInt64]$WorkspaceId, [int]$headerRow, [int]$primaryColumn ) $Headers = Get-Headers -ContentType 'text/csv' -ContentDisposition 'attachment' # convert input to csv $inputCsv = $input | ConvertTo-Csv $inputString = $inputCsv | Out-String $encoder = New-Object System.Text.UTF8Encoding $body = $encoder.GetBytes($inputString) If ($FolderId) { $Uri = "{0}/folders/{1}/sheets/import?sheetName={2}" -f $BaseURI, $folderId, $SheetName } elseIf ($WorkspaceId) { $Uri = "{0}/workspaces/{1}/sheets/import?sheetName={2}" -f $BaseURI, $WorkspaceId, $SheetName } else { $Uri = "{0}/sheets/import?sheetName={1}" -f $BaseURI, $SheetName } if ($headerRow -ge 0) { $Uri = "{0}&headerRowIndex={1}" -f $Uri, $headerRow } if ($PrimaryColumn) { $Uri = "{0}&primaryColumnIndex={1}" -f $Uri, $PrimaryColumn } $response = Invoke-RestMethod -Method POST -Uri $Uri -Headers $Headers -Body $body if ($response.message -ne "SUCCESS") { Throw "Import failed! $($_.Exception.Message)" } return $result <# .SYNOPSIS Exports a powershell array into a new Smartsheet. .DESCRIPTION Exports an array of PSObjects into a new smartsheet. This function will always create a new sheet even if there is a sheet of the same name. The API will attempt to determine column types. .PARAMETER InputObject Array of object to create the Smartsheet from. .PARAMETER SheetName The name of the new Smartsheet. .PARAMETER FolderId The folder ID of the folder to create the Smartsheet in. This can either be a folder from the home location or a folder in a Workspace. Use the Get-SmartsheetFolder or Get-SmartsheetWorkspaceFolders to get the Folder Id. .PARAMETER WorkspaceId The workspace to create the Smartsheet in. Use the Get-SmartsheetWorkspaces function to get the Workspace Id. This will create the sheet in the root of the workspace. To create a sheet in a folder in a workspace, specify the folder ID of the folder inside the workspace. At this time you cannot get a recursive list of all folders in a workspace, You can get a recursive list of all subfolders of a workspace folder. Use the Get-SmartsheetFolders function, specifying the top level folder ID and the Recursive property. .PARAMETER headerRow Row to use for column headers. All rows above this row are ignored. If omitted the first row will be used. A value of -1 will create default headers in the form Column1, Column1, etc. .PARAMETER primaryColumn The column to use as the primary column. default is the 1st column. .NOTES As mentioned this function ALWAYS creates a new sheet even if the name already exists. Name IS NOT the unique identifyer in smartsheet, the sheet ID is. If you want to update a shee use the Update-SmartSheet funcction. .EXAMPLE Create a new sheet in the home folder. $ObjectArray | Export-Smartsheet -SheetName "MyNewSheet" .EXAMPLE Create a new sheet in the folder. $Folder = Get-SmartsheetHomeFolders -Recurse | Where-object {$_.FullName like "Inventory/Westcoast"} $objectArray | Export-Smartsheet -SheetName "MyNewSheet" -folder 'myfolder1/myfolder2' .EXAMPLE Create a new sheet in a workspace folder. $Workspace = Get-SmartsheetWorkspaces | Where-Object {$_.Name -eq 'Accounting'} $APFolder = Get-SMartsheetWorkspaceFolders -WorkspaceId $Workspace.Id | Where-Object ($_.Name -eq 'Accounts Payable') $PaymentsFolder = Get-SmartsheetFolders -FolderId $APFolder.Id -Recurse | Where-Object {$_.FullName -eq "Microsoft/Payments"} $ObjectArray | Export-Smartsheet -Sheetname 'July Payments' -folder $PaymentsFolder.Id #> } function Update-Smartsheet() { [CmdletBinding()] Param( [Parameter( Mandatory = $true, ValueFromPipeline = $true )] [psObject[]]$InputObject, [Parameter(Mandatory = $true)] [UInt64]$sheetId, [switch]$UseRowId, [switch]$PassThru ) $sheet = Get-Smartsheet -id $sheetId $RowsToUpdate = @() $RowsToInsert = @() $RowsToAdd = @() # Verify columns, column names must match properties of the objects in the array. $columns = $sheet.columns $PrimaryColumn = ($Columns.Where({ $_.Primary -eq $true }))[0] $pcIndex = $columns.IndexOf($PrimaryColumn) $properties = $input[0].PSObject.properties | Select-Object Name If ($UseRowId) { $properties = $properties | Select-Object -Skip 1 } if ($columns.count -ne $properties.count) { throw "Column count does not match!" } foreach ($prop in $properties) { $col = $columns.Where({ $_.title -eq $prop.Name }) if (-not $col) { throw "Column names do not match" } } [int]$RowsProcessed = 0 [int]$RowsUpdated = 0 foreach ($object in $input) { $RowhasChanged = $False Write-Host ("Row:{0}" -f ($Input.IndexOf($Object) + 1)) $RowsProcessed += 1 $props = $object.PSObject.properties | Select-Object Name, Value if ($UseRowId) { $props = $props | Select-Object -Skip 1 } $cells = @() try { foreach ($prop in $props) { Write-Host $prop.name #$Activity = "Row:{0}" -f ($Input.IndexOf($Object) + 1) #Write-Host $Activity #Write-Progress -Activity $Activity -Status $Prop.Name $index = $props.indexOf($prop) $column = $sheet.columns[$index] if ($column.formula) { Continue } switch ($column.type) { 'PICKLIST' { if ($prop.value) { if ($column.options) { if ($prop.value -notin $column.options) { $options = $column.options $options += $prop.value $column.options = $options $sheet = Set-SmartsheetColumn -Id $sheet.id -ColumnId $column.id -column $column -PassThru } } } } 'CHECKBOX' { If ($prop.value) { $Prop.value = [System.Convert]::ToBoolean($prop.value) } else { $prop.value = $false } #$prop.value = [System.Convert]::ToBoolean($prop.value) } } if ($null -eq $Prop.value) { $Prop.value = " " } $cell = New-SmartSheetCell -columnId $column.id -value $prop.value $cells += $cell } } catch { throw $_ } if ($UseRowId) { # Get to row to update based on the RowId Property $row = $sheet.rows.where({ $_.id -eq $Object.RowId }) } else { # Get the row based on a unique primary column. # Does the row exist based on the primary Column column $row = $sheet.rows.Where({ $_.cells[$pcIndex].value -eq $props[$pcIndex].value }) } If ($row -is [array]) { throw "Primary column is not unique!" } if ($row) { # Check to see if the data has changed foreach ($rowCell in $row.Cells) { $i = $row.Cells.indexOf($rowCell) if ($cells[$i].value -ne $rowCell.value) { $rowcell.value = $cells[$i].value $RowhasChanged = $true } } if ($RowhasChanged) { $RowsToUpdate += $row } # if ($RowNeedsUpdating) { # $RowsUpdated += 1 # $Time2 = Measure-Command {[void](Set-SmartsheetRow -id $sheetId -rowId $row.Id -Cells $cells)} # $Time2.TotalMilliseconds # } } else { $NewRow = [PSCustomObject]@{ cells = $cells } # $index = $input.IndexOf($object) # if ($index -lt ($sheet.rows.Count - 1)) { # $SiblingId = $sheet.rows[$index].id # $NewRow | Add-Member -MemberType NoteProperty -Name "siblingId" -Value $SiblingId # $RowsToInsert += $row # } # else { #$NewRow | Add-Member -MemberType NoteProperty -Name "ToBottom" -Value $true $RowsToAdd += $NewRow # } #$RowsUpdated += 1 } } # Update sheet if ($RowsToUpdate.Count -gt 0) { #[array]$arrRows = $RowsToUpdate.ToArray() [void](Set-SmartsheetRows -Id $sheetId -Rows ($RowsToUpdate | Select-Object id,cells)) } if ($RowsToAdd.count -gt 0) { [void](Add-SmartsheetRows -Id $sheetId -Rows $RowsToAdd) } $Stats = [PSCustomObject]@{ RowsProcessed = $RowsProcessed RowsChanged = ($RowsUpdated.Count + $RowsToAdd.Count) } $Stats | Format-Table if ($PassThru) { $newSheet = Get-Smartsheet -SheetId $sheet.id return $newSheet } <# .SYNOPSIS Update a smartsheet. .DESCRIPTION Update a Smartsheet from an array of powershell objects. 1. The number and names of the columns is the same as the properties in the object in the array. 2. If the array objects do not contain a property RowId then primary column is used to identify rows to be updated and must be unique. 3. If the Array objects contain the property RowId then this is used to identify the row to be updated. The primary column does not have to be unique. .PARAMETER InputObject An array of powershell objects. .PARAMETER sheetId The Id of the sheet to update. .PARAMETER UseRowId This assumes that the objects in the array have a property called RowId which contains the Smartsheet row Id for the data, This will update the row associated with that Row Id. .PARAMETER PassThru Return the updated sheet object. .NOTES To return an array of objects from a smartsheet that contains the row id of the row the values are associated with use the ToArray method on the sheet object returned by Get-Smartsheet passing a value of $true. $sheet.ToArray($true). Updating sheet rows based on the primary column is maintained for backward compatibility. You should use the RowId method in any new projects as it is more accurate and does not depend on the primary column being unique. Smartsheet does not force uniqueness on the primary column. Updating sheets by primary column may be removed from future versions of this module. .EXAMPLE Update the rows in the smartsheet based on Primary Columns. $Array | Update-Smartsheet -SheetId $sheet.id .EXAMPLE Update the rows in the smartsheet based on the RowId property. $Array | Update-Smartsheet -SheetId $Sheet.Id -UseRowId .EXAMPLE Update the rows in the smartsheet based on the RowId property and return the updated sheet. $Array | Update-Smartsheet -SheetId $Sheet.Id -UseRowId -PassThru #> } # End Export Functions |