the first try catch works, he is adding the ms graph AD group in office365.
But the second try catch (converting the group to a Team does not work. I receive an 404 error??
The groupID exists on the server I notice.
Whats going wrong to get an 404?
I did add a secret token on the azure control panel.
The script has the token, appid and tenantid in live time.
also: the tenant has graph roles like group.ReadWriteAll. same for Teams and Directories.
Clear-Host
$env:graphApiDemoAppId = "" # Replace with your Azure AD app id
$env:graphApiDemoAppSecret = "" # Replace with your Azure AD app secret
$env:tenantId = "" # Replace with your Azure AD tenant ID
$oauthUri = "https://login.microsoftonline.com/$env:tenantId/oauth2/v2.0/token"
# Create token request body
$tokenBody = #{
client_id = $env:graphApiDemoAppId
client_secret = $env:graphApiDemoAppSecret
scope = "https://graph.microsoft.com/.default"
grant_type = "client_credentials"
}
# Retrieve access token
$tokenRequest = Invoke-RestMethod -Uri $oauthUri -Method POST -ContentType "application/x-www-form-urlencoded" -Body $tokenBody -UseBasicParsing
# Save access token
$accessToken = ($tokenRequest).access_token
$headers = #{
"Authorization" = "Bearer $accessToken"
"Content-type" = "application/json"
}
try
{
# Create group request body
$groupName = "Test_Kenvh16"
$groupBodyParams = #{
displayName = $groupName
description = $groupName
mailNickName = $groupName
groupTypes = #("Unified")
mailEnabled = $true
securityEnabled = $false
visibility = "Private"
}
$groupBody = ConvertTo-Json -InputObject $groupBodyParams
$newGroup = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/groups" -Method POST -Headers $headers -Body $groupBody
$groupId = $newGroup.id
Write-Host "group created:" $groupName "id:" $groupId
try
{
#convert group to team..
$teamBodyParams = #{
memberSettings = #{
allowCreateUpdateChannels = $true
}
messagingSettings = #{
allowUserEditMessages = $true
allowUserDeleteMessages = $true
}
funSettings = #{
allowGiphy = $true
giphyContentRating = "strict"
}
}
$teamBody = ConvertTo-Json -InputObject $teamBodyParams
$teamUri = "https://graph.microsoft.com/v1.0/groups/" + $groupId + "/team"
$newTeam = Invoke-RestMethod -Uri $teamUri -Method POST -Headers $headers -Body $teamBody
$teamId = $newTeam.id
}
catch
{
Write-Host "createTeam ExceptionMessage:" $_.Exception.Message
Write-Host "createTeam StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "createTeam StatusDescription:" $_.Exception.Response.StatusDescription
}
}
catch
{
Write-Host "createGroup ExceptionMessage:" $_.Exception.Message
Write-Host "createGroup StatusCode:" $_.Exception.Response.StatusCode.value__
Write-Host "createGroup StatusDescription:" $_.Exception.Response.StatusDescription
}
Tried searching if there's any out of the box method to export import queries in new projects. Seems like there isn't any.
Can it be achieved by code? If yes, would like to know how.
Appreciate any tips.
Regards,
You can use the rest api to read and create queries: Queries
Powershell example to export queries into local folder:
$user = ""
$token = "<pat>" #https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
$teamProject = "team_project_name"
$orgUrl = "https://dev.azure.com/<org>"
$sourceQueryFolder = "Shared Queries/Change Management"
$targetLocalFolder = "c:/temp/Change Management Queries"
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$queriesUrl = "$orgUrl/$teamProject/_apis/wit/queries/$sourceQueryFolder"+"?`$depth=1&`$expand=all&api-version=5.0"
function InvokeGetRequest ($GetUrl)
{
return Invoke-RestMethod -Uri $GetUrl -Method Get -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
}
$resQuries = InvokeGetRequest $queriesUrl
if (![System.IO.Directory]::Exists($targetLocalFolder))
{
New-Item -Path $targetLocalFolder -ItemType "directory"
}
if ($resQuries.isFolder -and $resQuries.hasChildren)
{
foreach($item in $resQuries.children)
{
if (!$item.isFolder)
{
$queryJson = "{`"name`":`"{queryname}`", `"wiql`":`"{querywiql}`"}"
$queryJson = $queryJson -replace "{queryname}", $item.name
$queryJson = $queryJson -replace "{querywiql}", $item.wiql
$filepath = "$targetLocalFolder/" + $item.name
Set-Content -Path $filepath -Value $queryJson
}
}
}
Powershell example to import queries from local folder:
$user = ""
$token = "<pat>" #https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
$teamProject = "team_project_name"
$orgUrl = "https://dev.azure.com/<org>"
$sourceLocalFolder = "c:/temp/Change Management Queries"
$targetQueryFolder = "Shared Queries/Change Management Imported" #should exist
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$queriesUrl = "$orgUrl/$teamProject/_apis/wit/queries/$targetQueryFolder"+"?&api-version=5.0"
function InvokePostRequest ($PostUrl, $body)
{
return Invoke-RestMethod -Uri $PostUrl -Method Post -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
}
$files = Get-ChildItem -File -Path $sourceLocalFolder
foreach($wiqlfile in $files)
{
$wiqlBody = Get-Content $wiqlfile
InvokePostRequest $queriesUrl $wiqlBody
}
Example to copy a query folder content from one team project into another one in the same organization through rest API:
$user = ""
$token = "<pat>" #https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/use-personal-access-tokens-to-authenticate?view=azure-devops&tabs=preview-page
$teamProjectSource = "source_team_project_name"
$teamProjectTarget = "target_team_project_name"
$orgUrl = "https://dev.azure.com/<org>"
$sourceQueryFolder = "Shared Queries/Change Management"
$targetQueryFolder = "Shared Queries/Change Management" #should exist
$queryObject = [PSCustomObject]#{
name = $null
wiql = $null
columns = $null
sortColumns = $null
}
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$queriesPostUrl = "$orgUrl/$teamProjectTarget/_apis/wit/queries/$targetQueryFolder"+"?api-version=5.0"
$queriesGettUrl = "$orgUrl/$teamProjectSource/_apis/wit/queries/$sourceQueryFolder"+"?`$depth=1&`$expand=all&api-version=5.0"
function InvokeGetRequest ($GetUrl)
{
return Invoke-RestMethod -Uri $GetUrl -Method Get -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
}
function InvokePostRequest ($PostUrl, $body)
{
return Invoke-RestMethod -Uri $PostUrl -Method Post -ContentType "application/json" -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)} -Body $body
}
$resQuries = InvokeGetRequest $queriesGettUrl
if ($resQuries.isFolder -and $resQuries.hasChildren)
{
foreach($item in $resQuries.children)
{
if (!$item.isFolder)
{
$queryObject.name = $item.name
$queryObject.wiql = $item.wiql
$queryObject.columns = $item.columns
$queryObject.sortcolumns = $item.sortcolumns
$wiqlbody = ConvertTo-Json $queryObject -Depth 10
InvokePostRequest $queriesPostUrl $wiqlBody
}
}
}
You can use out of box function to export and import queries. Visual Studio and Team Explorer contain that.
Go to the Queries Tab:
Edit query
Save as
Select another project
When I issue:
$uri = "https://graph.microsoft.com/v1.0/users/jane.doe#blah.com/contacts?`$filter=emailAddresses/any(a:a/address eq 'fred.bloggs#blah.com')"
$method = "GET"
$query = Invoke-WebRequest -Method $method -Uri $uri -Headers #{Authorization = "Bearer $token"} -ErrorAction Stop
$querystring = $query.Content | out-string | out-host
I get back the following 3 records:
{"#odata.context":"https://graph.microsoft.com/v1.0/$metadata#users('jane.doe%40blah.com')/contacts","value":[{"#odata.etag":"W/\"EQAAABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC6/\"",
"id":"AAMkADViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNzA1MGUyOABGAAAAAADU7_adBLD4RZAfnn8KRZ6vBwDSyGTyvqlwS5n00f5wZ-_rAAAAAAEOAADSyGTyvqlwS5n00f5wZ-_rAAApEyC7AAA=","createdDateTime":"2019-09-09T1
3:39:27Z","lastModifiedDateTime":"2019-09-09T13:39:28Z","changeKey":"EQAAABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC6/","categories":[],"parentFolderId":"AQMkADViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNz
A1MGUyOAAuAAAD1O-mnQSw_EWQH55-CkWerwEA0shk8r6pcEuZ9NH_cGf-qwAAAgEOAAAA","birthday":null,"fileAs":"","displayName":"Fred Bloggs","givenName":"Fred","initials":null,"middleName":null,"nickName"
:null,"surname":"Bloggs","title":null,"yomiGivenName":null,"yomiSurname":null,"yomiCompanyName":null,"generation":null,"imAddresses":[],"jobTitle":null,"companyName":null,"department":null,"o
fficeLocation":null,"profession":null,"businessHomePage":null,"assistantName":null,"manager":null,"homePhones":[],"mobilePhone":null,"businessPhones":["+1 732 555 0103"],"spouseName":null,"pe
rsonalNotes":"Note 3","children":[],"emailAddresses":[{"name":"Fred Bloggs","address":"fred.bloggs#blah.com"}],"homeAddress":{},"businessAddress":{},"otherAddress":{}},{"#odata.etag":"W/\"EQA
AABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC62\"","id":"AAMkADViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNzA1MGUyOABGAAAAAADU7_adBLD4RZAfnn8KRZ6vBwDSyGTyvqlwS5n00f5wZ-_rAAAAAAEOAADSyGTyvqlwS5n00f5wZ-_rAAAp
EyC6AAA=","createdDateTime":"2019-09-09T13:39:15Z","lastModifiedDateTime":"2019-09-09T13:39:15Z","changeKey":"EQAAABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC62","categories":[],"parentFolderId":"AQMkA
DViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNzA1MGUyOAAuAAAD1O-mnQSw_EWQH55-CkWerwEA0shk8r6pcEuZ9NH_cGf-qwAAAgEOAAAA","birthday":null,"fileAs":"","displayName":"Fred Bloggs","givenName":"Fred","in
itials":null,"middleName":null,"nickName":null,"surname":"Bloggs","title":null,"yomiGivenName":null,"yomiSurname":null,"yomiCompanyName":null,"generation":null,"imAddresses":[],"jobTitle":nul
l,"companyName":null,"department":null,"officeLocation":null,"profession":null,"businessHomePage":null,"assistantName":null,"manager":null,"homePhones":[],"mobilePhone":null,"businessPhones":
["+1 732 555 0102"],"spouseName":null,"personalNotes":"Note 2","children":[],"emailAddresses":[{"name":"Fred Bloggs","address":"fred.bloggs#blah.com"}],"homeAddress":{},"businessAddress":{},"
otherAddress":{}},{"#odata.etag":"W/\"EQAAABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC6r\"","id":"AAMkADViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNzA1MGUyOABGAAAAAADU7_adBLD4RZAfnn8KRZ6vBwDSyGTyvqlwS5n00f5
wZ-_rAAAAAAEOAADSyGTyvqlwS5n00f5wZ-_rAAApEyC5AAA=","createdDateTime":"2019-09-09T13:38:47Z","lastModifiedDateTime":"2019-09-09T13:38:48Z","changeKey":"EQAAABYAAADSyGTyvqlwS5n00f5wZ/+rAAApDC6r
","categories":[],"parentFolderId":"AQMkADViZDA1ZTI5LTg0NWEtNGJhYy05YzA3LWI3MjUxNzA1MGUyOAAuAAAD1O-mnQSw_EWQH55-CkWerwEA0shk8r6pcEuZ9NH_cGf-qwAAAgEOAAAA","birthday":null,"fileAs":"","displayN
ame":"Fred Bloggs","givenName":"Fred","initials":null,"middleName":null,"nickName":null,"surname":"Bloggs","title":null,"yomiGivenName":null,"yomiSurname":null,"yomiCompanyName":null,"generat
ion":null,"imAddresses":[],"jobTitle":null,"companyName":null,"department":null,"officeLocation":null,"profession":null,"businessHomePage":null,"assistantName":null,"manager":null,"homePhones
":[],"mobilePhone":null,"businessPhones":["+1 732 555 0101"],"spouseName":null,"personalNotes":"Note 1","children":[],"emailAddresses":[{"name":"Fred Bloggs","address":"fred.bloggs#blah.com"}
],"homeAddress":{},"businessAddress":{},"otherAddress":{}}]}
Is there a preferred way to parse this into an array with a row for each record?
Thanks.
Using PowerShell, you can convert the response to an object using the ConvertFrom-Json cmdlet
One option would be to utilize ConvertFrom-Json which converts JSON string to a custom object, for example:
$uri = "https://graph.microsoft.com/v1.0/users/"
$resp = Invoke-WebRequest -Method "GET" -Uri $uri -Headers #{Authorization = "Bearer $token"}
$data = $resp.Content | ConvertFrom-Json
$data.value | ForEach-Object { #enumerate collection
Write-Host $_.userPrincipalName
}
Another option is utilize Invoke-RestMethod instead of Invoke-WebRequest which auto converts JSON string to a custom object:
$uri = "https://graph.microsoft.com/v1.0/users/"
$data = Invoke-RestMethod -Headers #{Authorization = "Bearer $token"} -Method GET -Uri $uri
$data.value | ForEach-Object { #enumerate collection
Write-Host $_.userPrincipalName
}
I am trying to dismiss/close risk events using an automated process. For this we would need to make use of Graph API https://graph.microsoft.com/beta/riskyUsers/dismiss
I did searched for examples and couldn't find any.
I did tried using Post man by providing OAuth Bearer token in the header and I did tried with a PowerShell script and I also tried with .Net example provided in the page. None of them worked
PowerShell Code
{
$body = #{grant_type="client_credentials";resource=$resource;client_id=$ClientID;client_secret=$ClientSecret}
$oauthResponse = Invoke-RestMethod -Method POST -Uri $loginURL/$TenantName/oauth2/token?api-version=1.0 -Body $body
return $oauthResponse
}
$loginURL = "https://login.windows.net"
$resource = "https://graph.microsoft.com"
$ClientSecret="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$ClientID="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
$TenantName="xxxxx.com"
$oauth=RefreshToken -loginURL $loginURL -resource $resource -ClientID $ClientID -clientSecret $ClientSecret -tenantName $TenantName
$headerParams = #{'Authorization'="$($oauth.token_type) $($oauth.access_token)"}
$url="https://graph.microsoft.com/beta/riskyUsers/dismiss"
$userIds=#()
$userIds+="xxxxx-xxxxx-xxxxx-xxxxx-xxxxx"
$body=(#{"userIds"=$userIds})|convertto-json
$Response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url -Body $body -Method Post -ContentType "application/Json"
Response:
Invoke-WebRequest : The remote server returned an error: (500) Internal Server Error.
At C:\SourceCode\MIMSolution\PowerShellScripts\AzurePSMA\RiskyIdentityEvents\ExportScript_debug.ps1:19 char:13
+ $Response = Invoke-WebRequest -UseBasicParsing -Headers $headerParams -Uri $url ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest], WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
C# Code:
static void Main(string[] args)
{
var userIdsList = new List<String>();
userIdsList.Add("xxxxx-xxxxx-xxxxx-xxxxx-xxxx");
dismissUser(userIdsList);
}
static async void dismissUser(List<string> userIDs)
{
ClientCredential clientCredential = new ClientCredential("xxxxxxxxxxxxxxxxxxxxxxx");
string clientId = "xxxxxxxxxxxxxxxxxxxxxx";
IConfidentialClientApplication clientApplication = ClientCredentialProvider.CreateClientApplication(clientId, clientCredential);
ClientCredentialProvider authProvider = new ClientCredentialProvider(clientApplication);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
await graphClient.RiskyUsers.Dismiss(userIDs).Request().PostAsync();
}```
Exception:
{"Code: generalException\r\nMessage: An error occurred sending the request.\r\n"}
Inner Exception:
{"Code: generalException\r\nMessage: Unexpected exception occured while authenticating the request.\r\n"}
Finally after multiple trails , I have figured out the solution:
Issue was with the token which as OAuth 1.0 earlier.
I did fixed this snippet that request OAuth 2.0 and the issue resolved:
function RefreshToken($loginURL,$ClientID,$clientSecret,$tenantName)
{ $body = #{grant_type="client_credentials";client_id=$ClientID;client_secret=$ClientSecret;scope="https://graph.microsoft.com/.default"}
$oauthResponse = Invoke-RestMethod -Method POST -Uri $loginURL/$TenantName/oauth2/v2.0/token -Body $body
return $oauthResponse
}
Please help in how to get the Release approver list for each release definition in TFS 2017 update 3.1.
Thanks & Regards
You can call the REST API (Definitions - Get) to retrieve the approver list.
Below PowerShell script can get the Release approver list for each release definition: (Just replace the parameters accordingly)
Param(
[string]$baseurl = "http://SERVER:8080/tfs/DefaultCollection",
[string]$projectName = "0511ScrumTFVC",
[string]$user = "Domain\user",
[string]$token = "password/PAT"
)
# Base64-encodes the Personal Access Token (PAT) appropriately
$base64AuthInfo = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $user,$token)))
$releasesuri = "$baseurl/$projectName/_apis/release/definitions"
$releasesresult = Invoke-RestMethod -Uri $releasesuri -Method Get -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$redids = $releasesresult.value.id
foreach ($redid in $redids)
{
$uri = "$baseurl/$projectName/_apis/release/definitions/$redid"
$approvers = Invoke-RestMethod -Uri $uri -Method Get -Headers #{Authorization=("Basic {0}" -f $base64AuthInfo)}
$preApprovers = $approvers.environments.preDeployApprovals.approvals.approver.displayName
$postApprovers = $approvers.environments.postDeployApprovals.approvals.approver.displayName
write-host "Release ID: $redid - preApprovers:"
foreach ($preApprover in $preApprovers)
{
write-host $preApprover
}
write-host "`nRelease ID: $redid - postApprovers:"
foreach ($postApprover in $postApprovers)
{
write-host $postApprover
}
}