Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 15 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
---
page_type: sample
products:
- office-project
- office-365
languages:
- powershell
extensions:
contentType: samples
platforms:
- REST API
createdDate: 11/23/2015 8:07:26 PM
---
---
page_type: sample
products:
- office-project
- office-365
languages:
- powershell
extensions:
contentType: samples
platforms:
- REST API
createdDate: 11/23/2015 8:07:26 PM
---
# Project REST Basic Operations

The Project REST Basic Operations demonstrates how to create and update a project using REST API

###Prerequisites
To use this Project Online REST code sample, you need the following:
* An Office 365 tenant with a Project license
* Project CSOM client library. It is available as a Nuget Package from [here](https://www.nuget.org/packages/Microsoft.SharePointOnline.CSOM/)
* PowerShell v4.0
* Latest ADAL.NET library. It is available as a Nuget Package from [here](https://www.nuget.org/packages/Microsoft.IdentityModel.Clients.ActiveDirectory/)
* For the files downloaded, please run "Unblock-File *" to unblock accessing the file.

###Modules
* [CreateProject](/createproject.ps1)
Expand Down
181 changes: 98 additions & 83 deletions ReST.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,70 @@
See LICENSE in the project root for license information.
#>

$ErrorActionPreference = "Stop" # http://technet.microsoft.com/en-us/library/dd347731.aspx
Set-StrictMode -Version "Latest" # http://technet.microsoft.com/en-us/library/dd347614.aspx

# PS helper methods to call ReST API methods targeting Project Online tenants
$global:fedAuthTicket = ''
$global:accessHeader = ''
$global:digestValue = ''

[Reflection.Assembly]::LoadFrom("$($PSScriptRoot)\Microsoft.IdentityModel.Clients.ActiveDirectory.dll") | Out-Null

function Get-AADAuthToken([Uri] $Uri)
{
# NOTE: Create an azure app and update $clientId and $redirectUri below
$clientId = ""
$redirectUri = "https://login.microsoftonline.com/common/oauth2/nativeclient"

$authority = "https://login.microsoftonline.com/common"
$resource = $Uri.GetLeftPart([System.UriPartial]::Authority);

$promptBehavior = [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior]::Always
$platformParam = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList $promptBehavior
$authenticationContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority, $False
$authenticationResult = $authenticationContext.AcquireTokenAsync($resource, $clientId, $redirectUri, $platformParam).Result

return $authenticationResult
}

function Set-SPOAuthenticationTicket([string] $siteUrl)
{
$username = "admin@contoso.microsoft.com"
Write-Host 'Enter password for user' $username 'on site' $siteUrl
$password = Read-Host -AsSecureString
$siteUri = New-Object Uri -ArgumentList $siteUrl

$authResult = Get-AADAuthToken -Uri $siteUri
if ($authResult -ne $null)
{
$global:accessHeader = $authResult.AccessTokenType + " " + $authResult.AccessToken
}

# load the SP client runtime code
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
$onlineCredentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)
if ($onlineCredentials -ne $null)
{
$global:fedAuthTicket = $onlineCredentials.GetAuthenticationCookie($SiteUrl, $true).TrimStart('SPOIDCRL=')
}

if ([String]::IsNullOrEmpty($global:fedAuthTicket))
{
throw 'Could not obtain authentication ticket based on provided credentials for specified site'
}
if ([String]::IsNullOrEmpty($global:accessHeader))
{
throw 'Could not obtain authentication ticket based on provided credentials for specified site'
}
}

function Build-ReSTRequest([string] $siteUrl, [string]$endpoint, [string]$method, [string]$body = $null)
{
$url = ([string]$siteUrl).TrimEnd("/") + "/_api/" + $endpoint
$req = [System.Net.WebRequest]::Create($url)
$req.Method = $method

[bool]$isReadOnly = (('GET','HEAD') -contains $req.Method)
[bool]$isDigestRequest = $endpoint -contains 'contextinfo'

if ([String]::IsNullOrEmpty($body))
{
$req.ContentLength = 0;
}
else
{
$req.ContentLength = $body.Length
$req.ContentType = "application/json"
}

$domain = (New-Object System.Uri($url)).Authority
$cookies = New-Object System.Net.CookieContainer
$fedCookie = New-Object System.Net.Cookie 'SPOIDCRL', $global:fedAuthTicket, "", $domain
$cookies.Add($fedCookie)
$url = ([string]$siteUrl).TrimEnd("/") + "/_api/" + $endpoint
$req = [System.Net.WebRequest]::Create($url)
$req.Timeout = 120000
$req.Method = $method

$req.CookieContainer = $cookies
[bool]$isReadOnly = (('GET','HEAD') -contains $req.Method)
[bool]$isDigestRequest = $endpoint -contains 'contextinfo'

if ([String]::IsNullOrEmpty($body))
{
$req.ContentLength = 0;
}
else
{
$req.ContentLength = $body.Length
$req.ContentType = "application/json"
}

# set Authorization header
$req.Headers.Add("Authorization", $global:accessHeader)

if (-not $isDigestRequest)
{
Expand All @@ -60,57 +75,57 @@ function Build-ReSTRequest([string] $siteUrl, [string]$endpoint, [string]$method
$req.Headers.Add("X-RequestDigest", $global:digestValue)
}
}
if (-not [String]::IsNullOrEmpty($body))
{
$writer = New-Object System.IO.StreamWriter $req.GetRequestStream()
$writer.Write($body)
$writer.Close()
if (-not [String]::IsNullOrEmpty($body))
{
$writer = New-Object System.IO.StreamWriter $req.GetRequestStream()
$writer.Write($body)
$writer.Close()
$writer.Dispose()
}
return $req
}
return $req
}

function Set-DigestValue([string]$siteUrl)
{
$request = Build-ReSTRequest $siteUrl 'contextinfo' 'POST' $null
if ($request -eq $null)
{
throw 'Could not obtain a request digest value based on provided credentials for specified site'
}
try
{
$resp = $request.GetResponse()
$reader = [System.Xml.XmlReader]::Create($resp.GetResponseStream())
if ($reader.ReadToDescendant("d:FormDigestValue"))
{
$global:digestValue = $reader.ReadElementContentAsString()
}
else
{
throw 'Could not obtain a request digest value based on provided credentials for specified site'
}
}
finally
{
if ($reader -ne $null)
{
$reader.Close()
$reader.Dispose()
}
if ($resp -ne $null)
{
$resp.Close()
$resp.Dispose()
}
}
$request = Build-ReSTRequest $siteUrl 'contextinfo' 'POST' $null
if ($request -eq $null)
{
throw 'Could not obtain a request digest value based on provided credentials for specified site'
}
try
{
$resp = $request.GetResponse()
$reader = [System.Xml.XmlReader]::Create($resp.GetResponseStream())
if ($reader.ReadToDescendant("d:FormDigestValue"))
{
$global:digestValue = $reader.ReadElementContentAsString()
}
else
{
throw 'Could not obtain a request digest value based on provided credentials for specified site'
}
}
finally
{
if ($reader -ne $null)
{
$reader.Close()
$reader.Dispose()
}
if ($resp -ne $null)
{
$resp.Close()
$resp.Dispose()
}
}
}

function Post-ReSTRequest([string]$siteUrl, [string]$endpoint, [string]$body = $null)
{
$request = Build-ReSTRequest $siteUrl $endpoint 'POST' $body
$request = Build-ReSTRequest $siteUrl $endpoint 'POST' $body
$resp = $request.GetResponse()
if ($resp -ne $null)
{
Expand All @@ -122,7 +137,7 @@ function Post-ReSTRequest([string]$siteUrl, [string]$endpoint, [string]$body = $

function Patch-ReSTRequest([string]$siteUrl, [string]$endpoint, [string]$body)
{
$request = Build-ReSTRequest $siteUrl $endpoint 'PATCH' $body
$request = Build-ReSTRequest $siteUrl $endpoint 'PATCH' $body
$resp = $request.GetResponse()
if ($resp -ne $null)
{
Expand All @@ -134,12 +149,12 @@ function Patch-ReSTRequest([string]$siteUrl, [string]$endpoint, [string]$body)

function Get-ReSTRequest([string]$siteUrl, [string]$endpoint)
{
$request = Build-ReSTRequest $siteUrl $endpoint 'GET'
$request = Build-ReSTRequest $siteUrl $endpoint 'GET'
$resp = $request.GetResponse()
if ($resp -ne $null)
{
$reader = New-Object System.IO.StreamReader $resp.GetResponseStream()
$reader.ReadToEnd()
$reader.Dispose()
$reader.Dispose()
}
}
60 changes: 30 additions & 30 deletions createproject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ Set-DigestValue $siteUrl
# Project parameters as JSON payload
$projectid = [Guid]::NewGuid()
$body = "{
'parameters': {
'Id': '$projectid',
'Name': 'Project_$projectid',
'Description': 'Created from PowerShell using REST API'
}
'parameters': {
'Id': '$projectid',
'Name': 'Project_$projectid',
'Description': 'Created from PowerShell using REST API'
}
}"

# ReST request to create a project
Expand All @@ -49,13 +49,13 @@ Post-ReSTRequest $SiteUrl "ProjectServer/Projects('$projectid')/checkOut" $null
# Task parameters as JSON payload
$taskid = [Guid]::NewGuid()
$body = "{
'parameters': {
'Id': '$taskid',
'Name': 'Task_$taskid',
'Notes': 'Created from PowerShell using REST API',
'Start': '2016-01-04T08:00:00',
'Duration': '5d'
}
'parameters': {
'Id': '$taskid',
'Name': 'Task_$taskid',
'Notes': 'Created from PowerShell using REST API',
'Start': '2016-01-04T08:00:00',
'Duration': '5d'
}
}"

# ReST request to create a task
Expand All @@ -64,10 +64,10 @@ Post-ReSTRequest $SiteUrl "ProjectServer/Projects('$projectid')/Draft/Tasks/Add"
# Resource parameters as JSON payload
$resourceid = [Guid]::NewGuid()
$body = "{
'parameters': {
'Id': '$resourceid',
'Name': 'Resource_$resourceid'
}
'parameters': {
'Id': '$resourceid',
'Name': 'Resource_$resourceid'
}
}"

# ReST request to create a local resource
Expand All @@ -76,10 +76,10 @@ Post-ReSTRequest $SiteUrl "ProjectServer/Projects('$projectid')/Draft/ProjectRes
# Enterprise resource parameters as JSON payload
$enterprise_resourceid = [Guid]::NewGuid()
$body = "{
'parameters': {
'Id': '$enterprise_resourceid',
'Name': 'EnterpriseResource_$enterprise_resourceid'
}
'parameters': {
'Id': '$enterprise_resourceid',
'Name': 'EnterpriseResource_$enterprise_resourceid'
}
}"

# ReST request to create an enterprise resource
Expand All @@ -90,23 +90,23 @@ Post-ReSTRequest $SiteUrl "ProjectServer/Projects('$projectid')/Draft/ProjectRes

# Assignment parameters as JSON payload
$body = "{
'parameters': {
'ResourceId': '$resourceid',
'TaskId': '$taskid',
'Notes': 'Created from PowerShell using REST API'
}
'parameters': {
'ResourceId': '$resourceid',
'TaskId': '$taskid',
'Notes': 'Created from PowerShell using REST API'
}
}"

# ReST request to create an assignment for the local resource
Post-ReSTRequest $SiteUrl "ProjectServer/Projects('$projectid')/Draft/Assignments/Add" $body

# Assignment parameters as JSON payload
$body = "{
'parameters': {
'ResourceId': '$enterprise_resourceid',
'TaskId': '$taskid',
'Notes': 'Created from PowerShell using REST API'
}
'parameters': {
'ResourceId': '$enterprise_resourceid',
'TaskId': '$taskid',
'Notes': 'Created from PowerShell using REST API'
}
}"

# ReST request to create an assignment for the enterprise resource
Expand Down
21 changes: 21 additions & 0 deletions getprojectscsom.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<#
Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
See LICENSE in the project root for license information.
#>


# Get list of projects using CSOM ReST API
param
(
# SharepointOnline project site collection URL
$SiteUrl = $(throw "SiteUrl parameter is required")
)
# Load ReST helper methods
. .\ReST.ps1

# Set up the request authentication
Set-SPOAuthenticationTicket $siteUrl
Set-DigestValue $siteUrl

# Get list of projects using CSOM ReST API
Get-ReSTRequest $SiteUrl "ProjectServer/Projects"
Loading