Powershell variables in array - powershell

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
}

Related

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
}

LastPass Enterprise API New User Request

I am trying to use the LastPass Enterprise API to automate the creation of new users in our system using Powershell. I cannot seem to get the call to the API correct in my code. I am almost certain it has something to do with the "data" object. This is the batch object I am passing through the body.
$lastPassObject = #{
cid = "G1TUROWN";
provhash = "N0TM!NE";
cmd = "batchadd";
data = #(
{
username = $email;
fullname = $firstName + " " + $lastName;
password = "Toys4Trucks22!";
password_reset_required = "true";
}
)
}
Here is my API call
Invoke-RestMethod -Uri "https://lastpass.com/enterpriseapi.php" -Method Post -Body $lastPassObject -ContentType "application/json"
Followed by the Error I am receiving
Reference to the API: https://support.lastpass.com/help/add-new-users-via-lastpass-api
You need to convert your body to json before sending it.
Also, you put your Data section in a scriptblock within an array.
This need to be a hashtable, not a scriptblock.
$lastPassObject = #{
cid = "G1TUROWN";
provhash = "N0TM!NE";
cmd = "batchadd";
data = #(
# This need to be an hashtable. You were missing the #
#{
username = $email;
fullname = $firstName + " " + $lastName;
password = "Toys4Trucks22!";
password_reset_required = $true;
}
)
}
$Body = $lastPassObject | ConvertTo-Json
Invoke-RestMethod -Uri "https://lastpass.com/enterpriseapi.php" -Method Post -Body $Body -ContentType "application/json"
If you still have issues after that, make sure to check what the actual json look like (after ConvertTo-Json) so you know exactly what you are sending and can spot more easily discrepancies. For instance, when I first did it, I immediately saw that the data section was all wrong, formatting wise and spotted the missing # because of that.
Also, still by looking at the converted json and the example from their doc, you can see that password_reset_required is a boolean. I changed your "true" to $true so that the correlating json would be what was expected.

Using powershell to process emails in an o365 mailbox when connecting with application id and secret

I am writing a PowerShell script which reads through emails in an o365 email box.
It has to connect to the inbox, read through emails and make a decision based on the subject line, and then open those emails with a specific subject line and download any attachments the email might contain to a folder.
It [then] has to move the processed mail message to a mailbox sub-folder.
I have done this in the past using Exchange web services, and basic authentication to connect, but basic authentication is no longer available, and so I am having to re-do the script using modern authentication techniques.
I have been given an application id and secret from my Azure AD admin's with permissions for the relevant mailbox.
I've been googling away, and have managed to get some way using Microsoft Graph - using this section of Powershell:
$ClientID = "my-client-id"
$DirectoryID = "my-directory-id"
$ClientSecret = "my-client-secret"
$Credential = ConvertTo-GraphCredential -ClientID $ClientID -ClientSecret $ClientSecret -DirectoryID $DirectoryID
$mailbox = Get-EXOMailbox -UserPrincipalName myemailaccount#mycompany.com
This successfully gets me a mailbox object, but from here I am at a loss as to how to go about retrieving emails from it for processing.
The Microsoft doco and Google isn't helping me much at present on how extract emails from the mailbox having obtained the object.
Any thoughts/suggestions or pointers to relevant tutorials?
I managed to figure out how to access emails just usiing application id and secret without
using any non-standard libraries. You'll need to install the Graph and powershell utilities,
but they're free from Microsoft, so I call them standard. Then import them into my powershell
script thus:
Import-Module Microsoft.Graph.Mail
Import-Module Microsoft.PowerShell.Utility
After that, use these methods to access your o365 email system using the microsoft graph
REST API.
# This AuthenticateWithSecret is a slightly modified version of the method
# described by https://adamtheautomator.com/powershell-graph-api/
function Get-AccessToken
{
param(
[string] $AppId,
[string] $TenantName,
[string] $AppSecret)
$Scope = "https://graph.microsoft.com/.default"
$Url = "https://login.microsoftonline.com/$TenantName/oauth2/v2.0/token"
# Add System.Web for urlencode
Add-Type -AssemblyName System.Web
# Create body
$Body = #{
client_id = $AppId
client_secret = $AppSecret
scope = $Scope
grant_type = 'client_credentials'
}
# Splat the parameters for Invoke-Restmethod for cleaner code
$PostSplat = #{
ContentType = 'application/x-www-form-urlencoded'
Method = 'POST'
# Create string by joining bodylist with '&'
Body = $Body
Uri = $Url
}
# Request the token!
$Request = Invoke-RestMethod #PostSplat
return $Request
}
# This method builds a header object that can be passed
# in with each request to the Graph REST API, for authentication
# purposes
function Get-Header
{
param($theRequest)
$tokenType = $theRequest.token_type
$accessToken = $theRequest.access_token
# Create header
$theHeader = #{
Authorization = "$($tokenType) $($accessToken)"
}
return $theHeader
}
# This method gets an object containing the email folders from
# a mailbox specified by its UserPrincipalName - which is typically
# the email address of the user concerned. By default it will return
# the first 200 folders it finds, but you can specify however many
# that you wish to return using the $numberOfFoldersToGet parameter.
function Get-Folders
{
param(
$Credential,
[string] $UserPrincipalName,
[int]$numberOfFoldersToGet=200)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/?`$top=$numberOfFoldersToGet"
$folderResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This is a little helper function to get the specific folder object
# from an array of folder objects. You specify the folder that you
# want in the array based o its displayName using the $folderNameToFind
# parameter.
function Get-FolderFromListOfFolders
{
param($listOfFolders,
$folderNameToFind)
$specificFolder = ""
# Yeah, yeah, I know - we're doing this the brute-force way - just
# looping through all the folders until we find the one we want.
# Unless you have an insane number of folders, this shouldn't take
# *that* long, but this loop could be re-written to use a nicer search
# if it's really that big a problem.
foreach($fdr in $allFolders)
{
$thisFolderName = $fdr.displayName
if($thisFolderName -eq $folderNameToFind)
{
$specificFolder = $fdr
break
}
}
return $specificFolder
}
# This function allows you to retrieve an object describing a specific
# mail folder in an o365 Outlook, which you can specify by name. It allows
# you to access any folder by name - not just the common ones.
function Get-SpecificFolder
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderName)
$allTheFolders = Get-Folders -Credential $Credential -UserPrincipalName $UserPrincipalName
$allFolders = $allTheFolders.value
$specificFolder = Get-FolderFromListOfFolders -listOfFolders $allFolders -folderNameToFind $folderName
$folderId = $specificFolder.id
$Header = Get-Header -theRequest $Credential
$theRestQuery = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId"
$folderResult = Invoke-RestMethod -Uri $theRestQuery -Headers $Header -Method Get -ContentType "application/json"
return $folderResult
}
# This function returns an object containing all the emails in a given
# Mail folder in Outlook in o365
function GetEmails
{
param(
$Credential,
[string] $UserPrincipalName,
[string] $folderId)
$Header = Get-Header -theRequest $Credential
$restUrl = "https://graph.microsoft.com/v1.0/users/$UserPrincipalName/mailFolders/$folderId/messages"
$emailResult = Invoke-RestMethod -Uri $restUrl -Headers $Header -Method Get -ContentType "application/json"
return $emailResult
}
You can use these methods in this way. First, you need to specify these variables
$ClientID = "My-azure-ad-client-id"
$DirectoryID = "My-directory-id"
$ClientSecret = "My-client-secret"
$MailboxName = "the-email-address-of-the-o365-mailbox-i-want-to-access"
$MailboxFolderName = "the-folder-name-of-the-mailfolder-containing-emails"
Then, you can get a credential object thus:
$Credential = Get-AccessToken -AppId $ClientID -TenantName $DirectoryID -AppSecret $ClientSecret
Then get your email folder object thus
$myfolder = Get-SpecificFolder -Credential $Credential -UserPrincipalName $MailboxName -folderName $MailboxFolderName
Then, get the id of your folder - this allows you to access any folder - even non-standard ones by name.
$folderId = $myfolder.id
Now, get the email objects from the folder
$emails = GetEmails -Credential $Credential -UserPrincipalName $MailboxName -folderId $folderId
Then get the actual array of emails
$theEmails = $emails.value
Now loop through your array of emails and do stuff with it.
foreach($email in $theEmails)
{
Write-Output $email.subject
}

Jira Rest Api in Powershell

Could you help me?
I am trying to create an issue in Jira using the Powershell Invoke-WebRequest cmdlet. And I am getting 400 Bad Request error.
I was able to send a successful request using Postman, so I made sure the body syntax is correct and I have sufficient rights.
My code:
$body = #{
"fields" = #{
"project"=
#{
"key"= "ProjectKey"
}
"summary"= "Test"
"description"= "Test"
"issuetype" =#{
"id"= "10705"
}
"priority"= #{
"id"= "18"
}
"reporter"= #{"name"= "MyName"}
}
}
$Headers = #{
Authorization = "Basic QWxla0Zblablablablablablabla" #I took it from Postman
}
$restapiuri = "https://jira.domain.com/rest/api/2/issue"
Invoke-RestMethod -Uri $restapiuri -ContentType "application/json" -Body $body -Method POST -Headers $Headers
for example, I can successfully execute
Invoke-RestMethod "https://jira.domain.com/rest/api/2/issue/createmeta" -Headers $Headers
I've already spent a lot of time-solving this problem, but still can't create an issue.
Any help, please 🙏🙏🙏
For basic authentication with Jira SERVER, the credentials need to be supplied within the header in Base64 encoding, which you need to do before supplying it via the Powershell Invoke-WebRequest method. Here's how to do it:
$username = "The username here"
$password = "The password or token here"
# Convert the username + password into a Base64 encoded hash for basic authentication
$pair = "${username}:${password}"
$bytes = [System.Text.Encoding]::ASCII.GetBytes($pair)
$base64 = [System.Convert]::ToBase64String($bytes)
$headers = #{ Authorization = "Basic $base64" }
Next, in PowerShell, if you build the body of the request as a table, like you've shown, don't don't need to wrap the table elements with inverted commas, just leave them as is, but you do need to convert the table to JSON format before submitting it, like this:
$body = #{
fields = #{
project = #{
key = "ProjectKey"
}
issuetype = #{
id = "10705" # Or refer to the Issue type by its name eg. name = "Story"
}
summary = "Test"
}
}
# Convert the body to JSON format
$body = $body | ConvertTo-Json -Depth 50
I'm assuming your $Uri string contains an actual URL to a Jira Server, not the example 'jira.domain.com'
Start with a simple request in the body, like the one I've shown, that contains only the minimum required to create the Issue, which will check your basic code is working before making the request more complex.

Access SharePoint expanded properties

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
}
}