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)
}
}
}
Related
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
}
$result = New-Object System.Collections.ArrayList
ForEach ($repoElement in $Repo.value)
{
$repoId = $repoElement.id
$BranchCreatorUrl = "https://dev.azure.com/xyz/_apis/git/repositories/$repoId/refs?api-version=6.1-preview.1"
$CreateorInfo = (Invoke-RestMethod -Uri $BranchCreatorUrl -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$url1= "https://dev.azure.com/xyz/_apis/policy/configurations?api-version=4.1"
$response = (Invoke-RestMethod -Uri $url1 -Method Get -UseDefaultCredential -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)})
$obj=[pscustomobject]#{
RepositoryName = $repoElement.name
RepositoryId = $repoId
BranchName = $CreateorInfo.value.name
PolicyName = $response.value.type.displayname
}
$result += $obj
}
Write-Output $result
The above code gives an output
I want to show all the branch name as string and not in the form of object ie this way {..,...,...,..}
If this is just about for-display formatting, i.e. what is shown in the implicitly table-formatted display output, you can call Format-Table explicitly and use a calculated column to apply custom formatting to the BranchName column:
# Sample result.
$result = [pscustomobject]#{
RepositoryName = 'name'
RepositoryId = 'id'
BranchName = 'branch1', 'branch2', 'branch3'
PolicyName = 'policy'
}
# Use explicit Format-Table formatting with custom formatting of the
# 'BranchName' column.
$result | Format-Table RepositoryName,
RepositoryId,
#{ n='BranchName'; e={ $_.BranchName -join ', ' } },
PolicyName
The above yields a tabular display whose BranchName column doesn't have the enclosing { ... }, which PowerShell uses to indicate that the column value is an array (perhaps confusingly, because it looks like a script block):
RepositoryName RepositoryId BranchName PolicyName
-------------- ------------ ---------- ----------
name id branch1, branch2, branch3 policy
As a follow-up to this question, instead of using a long array in the script I wanted to draw from a text file. So I replaced this:
$URLs = 'http://websiteone.com','http://websitetwo.com','http://websitethree.com'
with this
$URLs = Get-Content ./urlfile.txt
or (functionally the same as far I know) this
$URLs = #(Get-Content ./urlfile.txt)
But I end up with Invoke-RestMethod : The remote server returned an error: (400) Bad Request.
Incorporating the great response form my last question, my foreach loop looks like this:
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL","EXECUTABLE","THREAT_ENTRY_TYPE_UNSPECIFIED"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$Result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
if ( ([string]::IsNullOrEmpty($Result)) ) {} else {write-host $URL "ALERT: Safe browsing match!"}
}
... but this doesn't work if I create the array with the Get-Content cmdlet. If I run the script either way, then type $URLs, I get the exact same data returned. What am I doing wrong with get-content?
The Invoke-RestMethod cmdlet is there to make one Rest request at a time and can't take an array.
You will need to add a forEach loop to step through your $urls one at a time, something like this:
foreach($url in $urls){
$result = Invoke-RestMethod -Uri $url
#do something with $result
}
So to integrate into your sample from the previous question, you should have a urls.txt file which looks like this:
http://google.com
http://small.com
https://fast.com/
And then your code would look like this:
$URLs = get-content .\urls.txt
$HEADERS = #{ 'Content-Type' = "application/json" }
$GOOGLE_API_KEY='[API Key]'
$Uri = 'https://safebrowsing.googleapis.com/v4/threatMatches:find?key='+ $GOOGLE_API_KEY
foreach($URL in $URLs) {
$BODY = #([pscustomobject]#{"client" = #{"clientId" = "company"; "clientVersion" = "1.0"}; "threatInfo" = #{"threatTypes" = "MALWARE","SOCIAL_ENGINEERING","THREAT_TYPE_UNSPECIFIED","UNWANTED_SOFTWARE","POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{"url" = $URL}}})
$JSONBODY = $BODY | ConvertTo-Json
$result = Invoke-RestMethod -Method 'POST' -Uri $Uri -Body $JSONBODY -Headers $HEADERS
[pscustomObject]#{SiteName=$url;ThreatInfo=$result.Matches}
}
This would load up the list of $urls from your text file, then run a Rest Request on each, storing the result in $result. Finally, it will make a new PowerShell Object with the site name and show you if there are any matches from the Google SafeBrowsing API.
You'll need to run the command interactively and see which properties from $result are meaningful to you, but you can see all of the expected properties in the Google API Docs.
Edit
Found the bug. It turns out when we use Get-Content the object returned back retains some of the document formatting information from the original file! We can see this by inspecting $JSONBODY. We also see that the conversion to Json from [PSCustomObject is leaving a lot of cruft behind too.
To fix this, we should cast $URL into a string using the ToString() method and also ditch casting to [psCustomObject] too as shown below.
$BODY = #{
"client" = #{
"clientId" = "company"; "clientVersion" = "1.0"
};
"threatInfo" = #{
"threatTypes" = "MALWARE",
"SOCIAL_ENGINEERING",
"THREAT_TYPE_UNSPECIFIED",
"UNWANTED_SOFTWARE",
"POTENTIALLY_HARMFUL_APPLICATION"; "platformTypes" = "ANY_PLATFORM"; "threatEntryTypes" = "URL"; "threatEntries" = #{
"url" = $URL.ToString()
}
}
}
$JSONBODY = $BODY | ConvertTo-Json
I have a couple of api calls that are bringing me info from our Microsoft 365 estate, specifically users and license info which I am happy with and then I am pushing this data to a MySQL db.
There are some users who have more than one product license, lets say 'Office 365' and 'Project Professional'.
When I run my script, the ForEach captures the first license in the System.Array but I cannot work out how to capture subsequent licenses for users.
Api's
$apiUrl = 'https://graph.microsoft.com/v1.0/subscribedSkus/'
$skus = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $apiUrl -Method Get
$skus = ($skus | select-object Value).Value
#####
$apiUrl = 'https://graph.microsoft.com/v1.0/users?$select=displayName,userPrincipalName,assignedLicenses'
$users = Invoke-RestMethod -Headers #{Authorization = "Bearer $($Tokenresponse.access_token)"} -Uri $apiUrl -Method GET
$users = ($users | select-object Value).Value
Current script:
ForEach($item in $users){
$itemSkuId = $item.assignedLicenses.skuId
ForEach($plan in $skus){
If($plan.skuId -in $itemSkuId){
$itemSkuId = $plan.skuPartNumber
$item.assignedLicenses = $itemSkuId
}
}
}
ForEach($item in $users){
if($item.assignedLicenses -notin $skus.skuPartNumber){
$item.assignedLicenses = ("No License Assigned")
}
}
Example output from Api call showing $users:
One has only 1 x license assigned to him, the other person has Two licenses assigned (diff products).
displayName userPrincipalName assignedLicenses
----------- ----------------- ----------------
Joe Bloggs Joe.Bloggs#test.com {#{disabledPlans=System.Object[]; skuId=cdd28e44-67e3-425e-be4c-737fab2899d3}}
Joe Bloggs2 Joe.Bloggs#test.com {#{disabledPlans=System.Object[]; skuId=29a2f828-8f39-4837-b8ff-c957e86abe3c}, #{disabledPlans=System.Object[]; s...
Essentially for every license assigned to a user it will give another skuId value.
I am trying to fetch asset data from the Fresh client with Powershell. I am able to get any asset by typing it's name but I want to save some of the variables it returns so I can use it further.
$naam = Read-Host "Voer product naam in"
# Set global variables
$APIKey = 'Myapikey'
$EncodedCredentials = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $APIKey,$null)))
$HTTPHeaders = #{ "X-ApiKey" = $APIKey}
$HTTPHeaders.Add('Authorization', ("Basic {0}" -f $EncodedCredentials))
$HTTPHeaders.Add('Content-Type', 'application/json')
$URL = 'https://helpdesk.company.nl/cmdb/items/list.json?field=name&q='+$naam
(Invoke-WebRequest -Method Get -Uri $URL -Headers $HTTPHeaders ).content
The following are some of the values that return after I run the above
{"config_items":[{"id":25000477949,"name":"SYS-MB1334","description":null,"ci_type_id":25000015988,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"
I would like to save the name and id variable for example
Unfortunately, the JSON you show is invalid.
Suppose the json returned from
$output = (Invoke-WebRequest -Method Get -Uri $URL -Headers $HTTPHeaders ).Content
looks like:
{"config_items":
[{"id":25000477949,"name":"SYS-MB1334","description":null,"ci_type_id":25000015988,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"},
{"id":12345678901,"name":"SYS-MB9876","description":null,"ci_type_id":12358745896,"impact":1,"created_at":"2020-03-12T10:14:40+01:00","updated_at":"2020-04-24T16:42:42+02:00"}]
}
Then you can collect the properties you need from the config_items using:
$result = ($output | ConvertFrom-Json).config_items |
Select-Object #{Name = 'Id'; Expression = {$_.id}},
#{Name = 'Name'; Expression = {$_.name}}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'X:\TheOutput.csv' -NoTypeInformation
Output on screen would look like
Id Name
-- ----
25000477949 SYS-MB1334
12345678901 SYS-MB9876
Hope that helps