Access SharePoint expanded properties - powershell

I'm accessing a SharePoint lists has an associated stakeholder entity--I'm having difficultly accessing the stakeholder's properties.
The 'primary' content's properties are located on xpath /feed/entry/content/properties/*. The stakeholder's properties are located on xpath /feed/entry/link/inline/entry/content/properties/*.
Assuming that I include the stakeholder's name in the odata query:
http://server/list/_vti_bin/listdata.svc/TheList?$select=Id,Title,Stakeholder/Name&$expand=Stakeholder
How do I reference the stakeholder's properties when enumeration the feed's properties?
Using this code, the Stakeholder.Name property is not populated:
(Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredentials).entry.content.properties | Foreach {
[PsCustomObject]#{
Id=$_.Id."#text";
Title=$_.Title;
StakholderName=$_.Stakeholder.Name;
}
}
Do I need to populate a second PsCustomObject for the stakeholder, then merge the 'primary' data?

The query is malformed since $ symbol have to be escaped using single-quoted string literals, for example:
$url = "http://contoso.intranet.com/_vti_bin/listdata.svc/TheList?`$select=Id,Title,Stakeholder/Name&`$expand=Stakeholder"
Then Stakeholder value could retrieved as demonstrated below:
$StakeholderValue = $data.link | where { $_.title -eq "Stakeholder" } | select -Property #{name="Name";expression={$($_.inline.entry.content.properties.Name)}}
Modified example
$url = "http://contoso.intranet.com/_vti_bin/listdata.svc/TheList?`$select=Id,Title,Stakeholder/Name&`$expand=Stakeholder"
$data = Invoke-RestMethod -Uri $url -Method Get -UseDefaultCredentials -ContentType "application/json;odata=verbose"
$data | Foreach {
[PsCustomObject]#{
Id = $_.content.properties.Id."#text";
Title = $_.content.properties.Title;
Stakeholder = $_.link | where { $_.title -eq "Stakeholder" } | select -Property #{name="Name";expression={$($_.inline.entry.content.properties.Name)}}
}
}
Alternatively i would propose to consider another approach. By default SharePoint 2010 REST service returns results in xml format. The idea is to return results in json format instead.
Unfortunately neither Invoke-RestMethod nor
Invoke-WebRequest could not be utilized for that purpose
since both of them contain a bug in PowerShell 3.0 according to
Microsoft Connect.
This particular bug prevents us to consume SharePoint REST services since
since Accept header could not be specified and therefore results could not
be returned in json format
Having said that, i would recommend to leverage WebClient Class.
Below is demonstrated the same example that returns results in JSON format. Note that getting of List Item properties become a way easier compared to the original example :
Function Execute-RequestJson()
{
Param(
[Parameter(Mandatory=$True)]
[string]$Url,
[Parameter(Mandatory=$False)]
[System.Net.ICredentials]$Credentials,
[Parameter(Mandatory=$False)]
[bool]$UseDefaultCredentials = $True,
[Parameter(Mandatory=$False)]
[Microsoft.PowerShell.Commands.WebRequestMethod]$Method = [Microsoft.PowerShell.Commands.WebRequestMethod]::Get
)
$client = New-Object System.Net.WebClient
if($Credentials) {
$client.Credentials = $Credentials
}
elseif($UseDefaultCredentials){
$client.Credentials = [System.Net.CredentialCache]::DefaultCredentials
}
$client.Headers.Add("Content-Type", "application/json;odata=verbose")
$client.Headers.Add("Accept", "application/json;odata=verbose")
$data = $client.DownloadString($Url)
$client.Dispose()
return $data | ConvertFrom-Json
}
$url = "http://contoso.intranet.dev/_vti_bin/listdata.svc/TheList?`$select=Id,Title,Stakeholder/Name&`$expand=Stakeholder"
$data = Execute-RequestJson -Url $url -UseDefaultCredentials $true
$data.d.results | Foreach {
[PsCustomObject]#{
Id = $_.Id;
Title = $_.Title;
Stakeholder = $_.Stakeholder.Name
}
}

Related

How to modify data in a format-table result?

Trying to make a powershell script that tracks link clicks.
I have three goals:
change destination to name
Filter out destinations that are not a file type
Only display name of file, not the URL or extension
# Set the API key and endpoint
$apiKey = "API-KEY"
$endpoint = "https://api.rebrandly.com/v1/links?&limit=100"
# Make the API call and store the response
$response = Invoke-RestMethod -Method GET -Uri $endpoint -Headers #{ "apikey" = $apiKey } | Format-Table -Property clicks, destination
$response
clicks destination
------ -----------
11 https://github.com/file1.ps1
4 https://github.com/file2.ps1
2 https://github.com/main.zip
1 https://www.instagram.com
Desired Result
clicks destination
------ -----------
11 file1
4 file2
2 main
Format-Table doesn't perform collection filtering hence is not the right cmdlet for this use case. Also, for 2 property objects, it is not needed at all since default formatting output will be a table by default for objects with 4 properties or less.
Invoke-RestMethod -Method GET -Uri $endpoint -Headers #{ "apikey" = $apiKey } | ForEach-Object {
$lastSegment = ($_.destination -as [uri]).Segments | Select-Object -Last 1
if([System.IO.Path]::GetExtension($lastSegment)) {
[pscustomobject]#{
Clicks = $_.Clicks
Name = [System.IO.Path]::GetFileNameWithoutExtension($lastSegment)
}
}
}
This is a modification of Santiagos answer.
I couldn't quite get his to work.
I'm sure this can be optimized however.
$apiKey = "API-KEY"
$endpoint = "https://api.rebrandly.com/v1/links?&limit=100"
$r = Invoke-RestMethod -Method GET -Uri $endpoint -Headers #{ "apikey" = $apiKey }
ForEach($l in $r){$s = ($l.destination -as [uri]).Segments[-1]
if([System.IO.Path]::GetExtension($s)) {
[pscustomobject]#{
Clicks = $l.Clicks
Name = [System.IO.Path]::GetFileNameWithoutExtension($s)
}
}
}

Alter object value upon assignment to PSCustomObject

I'm creating a log of attempted posts to an API. The API key is stored in a simple hash table and passed via Invoke-WebRequest:
$headers = #{ 'x-api-key' = 'ABC123DEF456GHI789' }
Try {
[Net.ServicePointManager]::SecurityProtocol = 'tls12, tls11'
$apiResponse = Invoke-WebRequest -Uri $url -Method $method -Headers $headers -Body $body
$status = $apiResponse.StatusCode
$statusDescription = $apiResponse.StatusDescription
} Catch {
$status = $_.Exception.Response.StatusCode.value__
$statusDescription = $_.Exception.Response.StatusDescription
}
I want to obscure the header key in the log, so I created and modified a new variable.
$obscured = $headers | ConvertTo-Json -depth 100 | ConvertFrom-Json
$obscured.'x-api-key' = $obscured.'x-api-key'.Substring(0,2) + '...' + $obscured.'x-api-key'.Substring($obscured.'x-api-key'.Length-2,2)
$logresults += [PSCustomObject]#{
status = $status
statusDescription = $statusDescription
url = $url
method = $method
header = $obscured
body = ConvertFrom-JSON $body
}
I want to retain the header's structure as a key/value pair in the log. The extra steps prepping a new variable seem wasteful. Does PowerShell have a way to change the header key value upon assignment to the PSCustomObject?
AFAIK, there is no easy way to obscure strings (or objects) in PowerShell or even .Net, see my related purpose: #16921 Add [HiddenString] Class. The only thing that exists is the gone crazy SecureString Class with difficult methodes to convert from a string and reveal string (as that is not secure). Besides, the SecureString might get obsolete (as it appears less secure than intended) and possibly replaced by a shrouded buffer which is even more difficult to use for obscuring information (if even possible in PowerShell).
Anyways, in the HiddenString idea you might do something like this:
$ApiKey = [HiddenString](Read-Host -AsSecureString 'Enter Api key:')
See also: How to encrypt/hide ClearText password in PowerShell Transcript
$Headers = #{
'Accept' = 'application/json'
'X-My-Header' = 'Hello World'
'x-api-key' = $ApiKey
}
$apiResponse = Invoke-WebRequest -Uri $url -Method $method -Headers $($Headers.'x-api-key' = $ApiKey.Reveal(); $Headers) -Body $body
$logresults += [PSCustomObject]#{ # Avoid +=, see: https://stackoverflow.com/a/60708579/1701026
status = $status
statusDescription = $statusDescription
url = $url
method = $method
header = $Headers
body = ConvertFrom-JSON $body
}

Powershell variables in array

I'm creating a powershell script to add users to a system via API request.
I have defined a variable below, it has a lot of escape characters because it's a json string which goes into the body of the request and otherwise it wasn't possible to define user data as variables in it, but this way it works.
$BODY_USERS = "{`"firstName`":`"${NAME}`", `"lastName`":`"${LASTNAME}`",`"email`":`"${EMAIL}`",`"enabled`":`"true`", `"username`":`"${USERNAME}`",`"credentials`":[{`"type`":`"password`",`"value`":`"Test12345789#`",`"temporary`":true}]}"
The command i execute to add a user is this one:
Invoke-RestMethod -Uri $URI_USERS -Method Post -Headers $HEADERS_USERS -Body $BODY_USERS
I would like to provide user related vars ($NAME, $LASTNAME etc) in a separate file. I did it for one user using dotsource - basically just defined abovementioned variables in a separate .ps1 file and referenced it in the script with . .\vars.ps1 and for one user it works just fine of course.
However if I need to construct a for loop for multiple users, I'm not sure what would be the best approach in this case.
Shall I define in my separate vars.ps1 file something like this:
$var=#($NAME='Jonny'; $LASTNAME='Doe'; $USERNAME='johnnydoe'; $EMAIL='johnny.doe#email.com')
$var#($NAME='Jonny2'; $LASTNAME='Doe2'; $USERNAME='johnnydoe2'; $EMAIL='johnny.doe2#email.com')
and then use something like Get-Content "C:\Users\Desktop\vars.ps1" | ForEach-Object {do blahblah} in my script? But I'm not sure how it will be consumed by my $BODY_USERS variable. So I need to provide an array of variables to a variable, or something like that...
Being novice to powershell, i'm a little bit puzzled here.
Any advice is appreciated!
As noted in comments, you don't need to construct JSON string manually. Create a hashtable for much cleaner syntax and convert to JSON string using ConvertTo-JSON. This also takes care of escaping characters that have special meaning in JSON, like " and \.
$BODY_USERS = #{
firstName = $NAME
lastName = $LASTNAME
email = $EMAIL
enabled = $true
username = $USERNAME
credentials = #(
#{ type = "password"; value = "Test12345789#"; temporary = $true }
)
}
$BODY_USERS_JSON = ConvertTo-JSON $BODY_USERS
Invoke-RestMethod -Uri $URI_USERS -Method Post -Headers $HEADERS_USERS -Body $BODY_USERS_JSON
For multiple users you could store the data in a CSV file, which are quite easy to handle with PowerShell:
Users.csv
name,lastname,username,email
Jonny,Doe,johnnydoe,johnny.doe#email.com
Jonny2,Doe2,johnnydoe2,johnny.doe2#email.com
Now we can process the users.csv like this:
$allUsers = Import-Csv users.csv
$allUsers # List users only for debugging purposes
foreach( $user in $allUsers ) {
$BODY_USERS = #{
firstName = $user.NAME
lastName = $user.LASTNAME
email = $user.EMAIL
enabled = $true
username = $user.USERNAME
credentials = #(
#{ type = "password"; value = "Test12345789#"; temporary = $true }
)
}
$BODY_USERS_JSON = ConvertTo-JSON $BODY_USERS
Invoke-RestMethod -Uri $URI_USERS -Method Post -Headers $HEADERS_USERS -Body $BODY_USERS_JSON
}

Removing item form JSON in powershell

I got a question to ask for removing an object in Json in powershell. I am fetching my results from powershell and i compressed it and placed into a JSON as shown below
foreach ($vm in $vms) {
$id = $vm.Config.InstanceUuid
$vmname = $vm.Config.Name
$geturl = "https://$nsxmanager/api/v1/fabric/virtual-machines?external_id=$id&included_fields=tags"
$getrequest = Invoke-RestMethod -Uri $geturl -Authentication Basic -Credential $nsxtcred -Method Get -ContentType "application/json" -SkipCertificateCheck
$getresult = $getrequest.results | ConvertTo-Json -Compress
Write-Host ($getresult)
}
This will get the results
{"tags":[{"scope":"allow_access","tag":"external"}, {"scope":"test","tag":"text"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
{"tags":[{"scope":"allow_access","tag":"external"}]}
I am trying to remove the element {"scope":"allow_access","tag":"external"} from all these results.
im not sure how i can remove it and wanted to seek some advice here how can I do so ?
You can try filtering out these results by converting your JSON results to a PSCustomObject, iterating through the Tags attribute in a nested Foreach loop, and building a new PSCustomObject that does not contain those results you would like to filter:
$getresult = $getresult | ConvertFrom-Json
$FilteredTagsArr = #()
Foreach($Tag in $GetResult.Tags){
If($Tag.scope -eq 'allow_access' -and $Tag.tag -eq "external"){
#Do Nothing
}Else{
$NewObj = [PSCustomObject]#{
Tag = $tag.tag
Scope = $tag.scope
}
$FilteredTagsArr += $NewObj
}
}
$GetResult.Tags = $FilteredTagsArr
Return $GetResult | ConvertTo-Json

Loading SharePoint Online pages with Invoke-Webrequest from Powershell

I want to be able to load SharePoint Online pages with Invoke-Webrequest from Powershell.
Can someone please show me how to successfully navigate past the login screen?
Here is how I get list items from SPO. You need to have "Microsoft.SharePoint.Client.SharePointOnlineCredentials" dll.
Then use the below function that I have modified for SPO.
Advantage: This will even work with older PowerShell versions. If you want to target only higher level of PowerShell then also this code will work. Optionally you can use Invoke-Webrequest instead of System.Net.HttpWebRequest and System.Net.HttpWebResponse.
function Get-ListItems {
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[String] $URL
)
#$URL = Fix-Url $URL
$xml = Request-Rest -URL $URL
return $xml
}
function Request-Rest{
[CmdletBinding()]
PARAM (
[Parameter(Mandatory=$true)]
[String] $URL,
[Parameter(Mandatory=$false)]
[Microsoft.SharePoint.Client.SharePointOnlineCredentials] $credentials,
[Parameter(Mandatory=$false)]
[String] $UserAgent = "PowerShell API Client",
[Parameter(Mandatory=$false)]
[Switch] $JSON,
[Parameter(Mandatory=$false)]
[Switch] $Raw
)
#Create a URI instance since the HttpWebRequest.Create Method will escape the URL by default.
$URI = New-Object System.Uri($URL,$true)
#Create a request object using the URI
$request = [System.Net.HttpWebRequest]::Create($URI)
#Build up a nice User Agent
$request.UserAgent = $(
"{0} (PowerShell {1}; .NET CLR {2}; {3})" -f $UserAgent, $(if($Host.Version){$Host.Version}else{"1.0"}),
[Environment]::Version,
[Environment]::OSVersion.ToString().Replace("Microsoft Windows ", "Win")
)
if ($credentials -eq $null)
{
$request.UseDefaultCredentials = $true
}
else
{
$request.Credentials = $credentials
}
if ($PSBoundParameters.ContainsKey('JSON'))
{
$request.Accept = "application/json"
}
$request.Headers.Add("X-FORMS_BASED_AUTH_ACCEPTED", "f")
#$request.Accept = "application/json;odata=verbose"
try
{
[System.Net.HttpWebResponse] $response = [System.Net.HttpWebResponse] $request.GetResponse()
}
catch
{
Throw "Exception occurred in $($MyInvocation.MyCommand): `n$($_.Exception.Message)"
}
$reader = [IO.StreamReader] $response.GetResponseStream()
if (($PSBoundParameters.ContainsKey('JSON')) -or ($PSBoundParameters.ContainsKey('Raw')))
{
$output = $reader.ReadToEnd()
}
else
{
[xml]$output = $reader.ReadToEnd()
}
$reader.Close()
Write-Output $output
$response.Close()
}
Though I do not know the direct way to pass the credentials along with Invoke-WebRequest itself, one workaround I found is, capture the cookie values by manual authenticating the SharePoint page and use those for subsequent requests. You can use fiddler or some other similar tool to grab the cookies. The two cookie names were 'FedAuth' and 'rtFa'
$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession
$cookieCollection = New-Object System.Net.CookieCollection
$cookie1 = New-Object System.Net.Cookie
$cookie1.Domain = "<your domain>.sharepoint.com"
$cookie1.Name = "FedAuth"
$cookie1.Value = "<cookie value here>"
$cookieCollection.Add($cookie1)
$cookie2 = New-Object System.Net.Cookie
$cookie2.Domain = "<your domain>.sharepoint.com"
$cookie2.Name = "rtFa"
$cookie2.Value = "<cookie value here>"
$cookieCollection.Add($cookie2)
$session.Cookies.Add($cookieCollection)
$uri = "https:<your site collection here>/_layouts/15/SharePointDesignerSettings.aspx"
$response = Invoke-WebRequest -Uri $uri -WebSession $session -Method Default
$form = $response.Forms[0]
You can use $form to examine the Html elements. If you want to submit the changes made to form use the below line
$response = Invoke-WebRequest -Uri $uri -WebSession $session -Method POST -Body $form
Note: There is an issue with Invoke-WebRequest with respect to field submission.. basically it uses 'id' of input element instead of 'name' in form fields collection.. the below url has the code to convert field Id to Name
https://d-fens.ch/2013/05/11/invoke-webrequest-uses-id-attribute-of-input-element-as-field-name-in-form-fields-collection/
If you are looking for the final content of a page, Invoke-WebRequest will not do what you need. Much of the content of a SharePoint page is loaded asynchronously using JavaScript. Invoke-WebRequest will only return the initial HTML content from the page.
What kind of content are you looking for from the page? Most everything about SharePoint can be accessed using RESTful queries (Invoke-RESTMethod and the SharePoint REST API) or from the PowerShell SharePoint PNP and SharePoint Online cmdlet libraries.