diff --git a/.gitignore b/.gitignore index 81c7661b6f91..8b0fa84f2d41 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ chocoapps.cache *.log .vscode/ Cache_BestPracticeAnalyser/ +Cache_DomainAnalyser/ ExcludedTenants diff --git a/AddAPDevice/run.ps1 b/AddAPDevice/run.ps1 index e9c400b6b699..a09a2e208628 100644 --- a/AddAPDevice/run.ps1 +++ b/AddAPDevice/run.ps1 @@ -11,9 +11,16 @@ Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -messa Write-Host "PowerShell HTTP trigger function processed a request." $TenantFilter = (Get-Content Tenants.cache.json | ConvertFrom-Json | Where-Object { $_.defaultdomainname -eq $Request.body.TenantFilter }).customerid $GroupName = if ($Request.body.Groupname) { $Request.body.Groupname } else { New-Guid } -$rawDevices = ($Request.body.Devices | ConvertFrom-Csv -Header "SerialNumber", "oemManufacturerName", "modelName", "productKey", "hardwareHash" -Delimiter ",") +$rawDevices = if ($Request.body.devices -like "Device serial number,Windows product ID,Hardware hash,Manufacturer name,Device Model*") { + Write-Host "csvupload" + ($Request.body.Devices | ConvertFrom-Csv -Delimiter "," -Header "SerialNumber", "productKey", "hardwareHash", "oemManufacturerName", "modelName") | Select-Object -Skip 1 +} +else { + Write-Host "Standard table request" + ($Request.body.Devices | ConvertFrom-Csv -Header "SerialNumber", "oemManufacturerName", "modelName", "productKey", "hardwareHash" -Delimiter ",") +} $Devices = ConvertTo-Json @($rawDevices) - +Write-Host $Devices $Result = try { $CurrentStatus = (New-GraphgetRequest -uri "https://api.partnercenter.microsoft.com/v1/customers/$tenantfilter/DeviceBatches" -scope 'https://api.partnercenter.microsoft.com/user_impersonation') if ($groupname -in $CurrentStatus.items.id) { throw "This device batch name already exists. Please try with another name." } diff --git a/AddPolicy/run.ps1 b/AddPolicy/run.ps1 index e65c11e592fa..ca61ccb80c75 100644 --- a/AddPolicy/run.ps1 +++ b/AddPolicy/run.ps1 @@ -12,12 +12,23 @@ $displayname = $request.body.Displayname $description = $request.body.Description $AssignTo = if ($request.body.Assignto -ne "on") { $request.body.Assignto } $RawJSON = $Request.body.RawJSON + $results = foreach ($Tenant in $tenants) { try { - $CreateBody = '{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}' - $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations" -tenantid $tenant -type POST -body $CreateBody - $UpdateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations('$($CreateRequest.id)')/updateDefinitionValues" -tenantid $tenant -type POST -body $RawJSON - + switch ($Request.body.TemplateType) { + "Admin" { + $CreateBody = '{"description":"' + $description + '","displayName":"' + $displayname + '","roleScopeTagIds":["0"]}' + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations" -tenantid $tenant -type POST -body $CreateBody + $UpdateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/groupPolicyConfigurations('$($CreateRequest.id)')/updateDefinitionValues" -tenantid $tenant -type POST -body $RawJSON + } + "Device" { + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/deviceConfigurations" -tenantid $tenant -type POST -body $RawJSON + } + "Catalog" { + $CreateRequest = New-GraphPOSTRequest -uri "https://graph.microsoft.com/beta/deviceManagement/configurationPolicies" -tenantid $tenant -type POST -body $RawJSON + } + + } Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($Tenant) -message "Added policy $($Displayname)" -Sev "Error" if ($AssignTo) { $AssignBody = if ($AssignTo -ne "AllDevicesAndUsers") { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.' + $($AssignTo) + 'AssignmentTarget"}}]}' } else { '{"assignments":[{"id":"","target":{"@odata.type":"#microsoft.graph.allDevicesAssignmentTarget"}},{"id":"","target":{"@odata.type":"#microsoft.graph.allLicensedUsersAssignmentTarget"}}]}' } diff --git a/AddUser/run.ps1 b/AddUser/run.ps1 index 5f36a07f148f..9380e484d282 100644 --- a/AddUser/run.ps1 +++ b/AddUser/run.ps1 @@ -11,7 +11,7 @@ $userobj = $Request.body Write-Host "PowerShell HTTP trigger function processed a request." try { $licenses = ($userobj | Select-Object "License_*").psobject.properties.value - $aliasses = ($userobj.AddedAliasses).Split([Environment]::NewLine) + $Aliases = ($userobj.AddedAliases).Split([Environment]::NewLine) $password = if ($userobj.password) { $userobj.password } else { -join ('abcdefghkmnrstuvwxyzABCDEFGHKLMNPRSTUVWXYZ23456789$%&*#'.ToCharArray() | Get-Random -Count 12) } $UserprincipalName = "$($UserObj.username)@$($UserObj.domain)" $BodyToship = [pscustomobject] @{ @@ -27,7 +27,7 @@ try { "password" = $password } } | ConvertTo-Json - $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users" -tenantid $Userobj.tenantid-type POST -body $BodyToship -verbose + $GraphRequest = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users" -tenantid $Userobj.tenantid -type POST -body $BodyToship -verbose Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantid) -message "Created user $($userobj.displayname) with id $($GraphRequest.id) " -Sev "Info" $results.add("Created user.") $results.add("Username: $($UserprincipalName)") @@ -60,19 +60,19 @@ catch { } try { - if ($aliasses) { - foreach ($Alias in $aliasses) { + if ($Aliases) { + foreach ($Alias in $Aliases) { Write-Host $Alias New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)" -tenantid $Userobj.tenantid -type "patch" -body "{`"mail`": `"$Alias`"}" -verbose } New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)" -tenantid $Userobj.tenantid -type "patch" -body "{`"mail`": `"$UserprincipalName`"}" -verbose Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantid) -message "Added alias $($Alias) to $($userobj.displayname)" -Sev "Info" - $body = $results.add("Added aliasses: $($aliasses -join ',')") + $body = $results.add("Added Aliases: $($Aliases -join ',')") } } catch { Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $($userobj.tenantid) -message "Alias API failed. $($_.Exception.Message)" -Sev "Error" - $body = $results.add("We've failed to create the aliasses: $($_.Exception.Message)") + $body = $results.add("We've failed to create the Aliases: $($_.Exception.Message)") } if ($Request.body.CopyFrom -ne "") { $MemberIDs = "https://graph.microsoft.com/v1.0/directoryObjects/" + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($GraphRequest.id)" -tenantid $Userobj.tenantid).id diff --git a/Applications_Upload/run.ps1 b/Applications_Upload/run.ps1 index f3e156ac44a1..3ebe684a113b 100644 --- a/Applications_Upload/run.ps1 +++ b/Applications_Upload/run.ps1 @@ -29,7 +29,7 @@ $EncBody = @{ Try { $ApplicationList = (New-graphGetRequest -Uri $baseuri -tenantid $Tenant) | Where-Object { $_.DisplayName -eq $ChocoApp.ApplicationName } if ($ApplicationList.displayname.count -ge 1) { - Log-Request -api "ChocoAppUpload" -API "ChocoApp" -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) exists. Skipping this application" -Sev "Warning" + Log-Request -api "ChocoAppUpload" -tenant $($Tenant) -message "$($ChocoApp.ApplicationName) exists. Skipping this application" -Sev "Warning" continue } $NewApp = New-GraphPostRequest -Uri $baseuri -Body ($intuneBody | ConvertTo-Json) -Type POST -tenantid $tenant diff --git a/BestPracticeAnalyser_All/run.ps1 b/BestPracticeAnalyser_All/run.ps1 index 91c14a33b104..7c2996c2d853 100644 --- a/BestPracticeAnalyser_All/run.ps1 +++ b/BestPracticeAnalyser_All/run.ps1 @@ -44,11 +44,13 @@ $Result = [PSCustomObject]@{ UnusedLicensesCount = "" UnusedLicensesResult = "" UnusedLicenseList = "" + SecureScoreCurrent = "" + SecureScoreMax = "" + SecureScorePercentage = "" } # Starting the Best Practice Analyser -Log-request -API "BestPracticeAnalyser" -tenant $tenant -message "Started Best Practice Analyser Durable Function on $($tenant)" -sev "Info" - + # Get the Secure Default State try { $SecureDefaultsState = (New-GraphGetRequest -Uri "https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy" -tenantid $tenant) @@ -257,6 +259,18 @@ catch { Log-request -API "BestPracticeAnalyser" -tenant $tenant -message "Unused Licenses on $($tenant). Error: $($_.exception.message)" -sev "Error" } +# Get Secure Score +try { + $SecureScore = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/security/secureScores?`$top=1" -tenantid $tenant -noPagination $true + $Result.SecureScoreCurrent = $SecureScore.currentScore + $Result.SecureScoreMax = $SecureScore.maxScore + $Result.SecureScorePercentage = [int](($SecureScore.currentScore / $SecureScore.maxScore) * 100) + Log-request -API "BestPracticeAnalyser" -tenant $tenant -message "Secure Score on $($tenant) is $($Result.SecureScoreCurrent) / $($Result.SecureScoreMax)" -sev "Debug" +} +catch { + Log-request -API "BestPracticeAnalyser" -tenant $tenant -message "Secure Score Retrieval on $($tenant). Error: $($_.exception.message)" -sev "Error" +} + # Send Output of all the Results to the Stream $Result \ No newline at end of file diff --git a/BestPracticeAnalyser_List/run.ps1 b/BestPracticeAnalyser_List/run.ps1 index 151450611eed..e27cf5bc09b2 100644 --- a/BestPracticeAnalyser_List/run.ps1 +++ b/BestPracticeAnalyser_List/run.ps1 @@ -11,7 +11,12 @@ Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -messa Write-Host "PowerShell HTTP trigger function processed a request." # Get all the things -$Results = Get-ChildItem ".\Cache_BestPracticeAnalyser\*.json" | ForEach-Object{Get-Content $_.FullName | Out-String | ConvertFrom-Json} +$UnfilteredResults = Get-ChildItem ".\Cache_BestPracticeAnalyser\*.json" | ForEach-Object{Get-Content $_.FullName | Out-String | ConvertFrom-Json} + +# Need to apply exclusion logic +$Skiplist = Get-Content "ExcludedTenants" | ConvertFrom-Csv -Delimiter "|" -Header "Name", "User", "Date" + +$Results = $UnfilteredResults | Where-Object {$_.Tenant -notin $Skiplist.Name} # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/DomainAnalyser_All/function.json b/DomainAnalyser_All/function.json new file mode 100644 index 000000000000..2d4ea9094b24 --- /dev/null +++ b/DomainAnalyser_All/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "tenant", + "direction": "in", + "type": "activityTrigger" + } + ] +} \ No newline at end of file diff --git a/DomainAnalyser_All/run.ps1 b/DomainAnalyser_All/run.ps1 new file mode 100644 index 000000000000..5b3e11615aa8 --- /dev/null +++ b/DomainAnalyser_All/run.ps1 @@ -0,0 +1,290 @@ +param($tenant) + +function Get-GoogleDNSQuery { + [CmdletBinding()] + param ( + [Parameter()] + [string] + $Domain, + + [Parameter()] + [string] + $RecordType, + + [Parameter()] + [bool] + $FullResultRecord = $False + ) + + try { + $Results = Invoke-RestMethod -Uri "https://dns.google/resolve?name=$($Domain)&type=$($RecordType)" -Method Get + } + catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Get Google DNS Query Failed with $($_.Exception.Message)" -sev Debug + } + + # Domain does not exist + if ($Results.Status -ne 0) { + return $null + } + + if (($Results.Answer | Measure-Object | Select-Object -ExpandProperty Count) -eq 0) { + return $null + } + else { + if ($RecordType -eq 'MX') { + $FinalClean = $Results.Answer | ForEach-Object { $_.Data.Split(' ')[1] } + return $FinalClean + } + if (!$FullResultRecord) { + return $Results.Answer + } + else { + return $Results + } + } +} + +$Domain = $Tenant.Domain + +Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Starting Processing of $($Tenant.Domain)" -sev Debug +$Result = [PSCustomObject]@{ + Tenant = $tenant.tenant + GUID = $($Tenant.Domain.Replace('.', '')) + LastRefresh = $(Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z') + Domain = $Domain + AuthenticationType = $Tenant.authenticationType + IsAdminManaged = $Tenant.isAdminManaged + IsDefault = $Tenant.isDefault + IsInitial = $Tenant.isInitial + IsRoot = $Tenant.isRoot + IsVerified = $Tenant.isVerified + SupportedServices = $Tenant.supportedServices + ExpectedSPFRecord = "" + ActualSPFRecord = "" + SPFPassTest = "" + SPFPassAll = "" + ExpectedMXRecord = "" + ActualMXRecord = "" + MXPassTest = "" + DMARCPresent = "" + DMARCFullPolicy = "" + DMARCActionPolicy = "" + DMARCReportingActive = "" + DMARCPercentagePass = "" + DNSSECPresent = "" + MailProvider = "" + DKIMEnabled = "" + Score = "" + MaximumScore = 160 + ScorePercentage = "" + ScoreExplanation = "" +} + +$Scores = [PSCustomObject]@{ + SPFPresent = 10 + SPFMSRecommended = 10 + SPFCorrectAll = 10 + MXMSRecommended = 10 + DMARCPresent = 10 + DMARCSetQuarantine = 20 + DMARCSetReject = 30 + DMARCReportingActive = 20 + DMARCPercentageGood = 20 + DNSSECPresent = 20 + DKIMActiveAndWorking = 20 +} + +$ScoreDomain = 0 +# Setup Score Explanation +[System.Collections.ArrayList]$ScoreExplanation = @() + +# Get 365 Service Configuration Records +$ServiceConfigRecords = New-GraphGetRequest -uri "https://graph.microsoft.com/v1.0/domains/$($domain)/serviceConfigurationRecords" -tenantid $tenant.tenant + +$Result.ExpectedMXRecord = $ServiceConfigRecords | Where-Object { $_.RecordType -eq "MX" } | Select-Object -ExpandProperty MailExchange +$Result.ExpectedSPFRecord = $ServiceConfigRecords | Where-Object { ($_.RecordType -eq "Txt") -and ($_.Text -like "*spf*") } | Select-Object -ExpandProperty Text + +# Get SPF Record +try { + $Results = Get-GoogleDNSQuery -Domain $domain -RecordType "TXT" + $SPFResults = $Results | Where-Object { $_.Data -like '*spf1*' } | Select-Object -ExpandProperty Data + if ($SPFResults.Count -gt 0) { + $Result.ActualSPFRecord = $SPFResults | Where-Object { $_ -like '*spf1*' } + $ScoreDomain += $Scores.SPFPresent + } + else { + $Result.ActualSPFRecord = "No SPF Record" + $ScoreExplanation.Add("No SPF Record Found") | Out-Null + } +} +catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Exception and Error while getting SPF Record with $($_.Exception.Message)" -sev Error +} + +# Check SPF Record +$SPFMatch = $Result.ActualSPFRecord | Where-Object { $_ -like "$($Result.ExpectedSPFRecord)" } +If (($SPFMatch | Measure-Object | Select-Object -ExpandProperty Count) -gt 0) { + $ScoreDomain += $Scores.SPFMSRecommended + $Result.SPFPassTest = $true + if ($SPFMatch -like '*-all*') { + $ScoreDomain += $Scores.SPFCorrectAll + $Result.SPFPassAll = $true + } + else { + $Result.SPFPassAll = $false + $ScoreExplanation.Add("SPF Record is not set to hard Fail") | Out-Null + } +} +else { + if ($Result.ActualSPFRecord -like '*include:spf.protection.outlook.com*') { + $ScoreDomain += $Scores.SPFMSRecommended + $Result.SPFPassTest = $true + if ($Result.ActualSPFRecord -like '*-all*') { + $ScoreDomain += $Scores.SPFCorrectAll + $Result.SPFPassAll = $true + } + else { + $Result.SPFPassAll = $false + } + } + else { + $ScoreExplanation.Add("SPF Record is Misconfigured") | Out-Null + $Result.SPFPassTest = $false + $Result.SPFPassAll = $false + } + +} + +# Get MX Record +try { + $MXResults = Get-GoogleDNSQuery -domain $domain -RecordType "MX" + $Result.ActualMXRecord = ($MXResults) -join "," +} +catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Exception and Error while getting MX Record with $($_.Exception.Message)" -sev Error +} + +# Check MX Record +$MXMatch = $Result.ActualMXRecord | Where-Object { $_ -like "*$($Result.ExpectedMXRecord)*" } +If (($MXMatch | Measure-Object | Select-Object -ExpandProperty Count) -gt 0) { + $Result.MXPassTest = $true + $ScoreDomain += $Scores.MXMSRecommended +} +else { + $Result.MXPassTest = $false + $ScoreExplanation.Add("MX Record does not match Microsoft's suggestion") | Out-Null +} + +# Get DMARC Record +try { + #$DMARCResults = Resolve-DnsName -Name "_dmarc.$($domain)" -Type TXT -ErrorAction SilentlyContinue | Where-Object { $_.Type -eq "TXT" } + $DMARCResults = Get-GoogleDNSQuery -Domain "_dmarc.$($domain)" -RecordType "TXT" + If ([string]::IsNullOrEmpty($DMARCResults.data)) { + $Result.DMARCPresent = $false + $ScoreExplanation.Add("No DMARC Records Found") | Out-Null + } + else { + $Result.DMARCPresent = $true + $ScoreDomain += $Scores.DMARCPresent + + $Result.DMARCFullPolicy = $DMARCResults.data + if (($Result.DMARCFullPolicy.replace(' ', '') -like '*;p=reject*')) { + $Result.DMARCActionPolicy = "Reject" + $ScoreDomain += $Scores.DMARCSetReject + } + if ($Result.DMARCFullPolicy -like '*p=n*') { + $Result.DMARCActionPolicy = "None" + $ScoreExplanation.Add("DMARC is not being enforced") | Out-Null + } + if ($Result.DMARCFullPolicy -like '*p=qua*') { + $Result.DMARCActionPolicy = "Quarantine" + $ScoreDomain += $Scores.DMARCSetQuarantine + $ScoreExplanation.Add("DMARC Partially Enforced with quarantine") | Out-Null + } + if (($Result.DMARCFullPolicy -like '*rua*') -or ($Result.DMARCHFullPolicy -like '*ruf*')) { + $Result.DMARCReportingActive = $true + $ScoreDomain += $Scores.DMARCReportingActive + } + else { + $Result.DMARCReportingActive = $False + $ScoreExplanation.Add("DMARC Reporting not Configured") | Out-Null + } + if ($Result.DMARCFullPolicy -like '*pct=*') { + if ($Result.DMARCFullPolicy -like '*pct=100*') { + $Result.DMARCPercentagePass = $true + $ScoreDomain += $Scores.DMARCPercentageGood + } + else { + $Result.DMARCPercentagePass = $false + $ScoreExplanation.Add("DMARC Not Checking All Messages") | Out-Null + } + } + else { + $Result.DMARCPercentagePass = $true + $ScoreDomain += $Scores.DMARCPercentageGood + } + } +} +catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Exception and Error while getting DMARC Record with $($_.Exception.Message)" -sev Error +} + +# DNS Sec Check +try { + $DNSSECResult = Get-GoogleDNSQuery -Domain "$($domain)" -RecordType "SOA" -FullResultRecord $true + if ($DNSSECResult.AD) { + $Result.DNSSECPresent = $true + $ScoreDomain += $Scores.DNSSECPresent + } + else { + $Result.DNSSECPresent = $false + $ScoreExplanation.Add("DNSSEC Not Configured or Enabled") | Out-Null + } +} +catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "Exception and Error while getting DNSSEC with $($_.Exception.Message)" -sev Error +} + +# DKIM Check +try { + # We can only really do Google and 365 DKIM so lets work out whether we are on Google or 365 + if ($Result.ActualMXRecord -like '*protection.outlook.com*') { + $Result.MailProvider = "Microsoft 365" + $DKIMSelector = "selector1._domainkey.$($domain)" + } + + if ($Result.ActualMXRecord -like '*l.google.com*') { + $Result.MailProvider = "Google" + $DKIMSelector = "google._domainkey.$($domain)" + } + + if ([string]::IsNullOrEmpty($Result.MailProvider)) { + $Result.MailProvider = "Unknown" + } + + if ($Result.MailProvider -ne 'Unknown') { + $DKIMResult = Get-GoogleDNSQuery -Domain $DKIMSelector -RecordType "TXT" + if ($DKIMResult.Data -like '*v=DKIM1*') { + $Result.DKIMEnabled = $true + $ScoreDomain += $Scores.DKIMActiveAndWorking + } + else { + $Result.DKIMEnabled = $false + $ScoreExplanation.Add("DKIM Not Configured") | Out-Null + } + } + +} +catch { + Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "DKIM Lookup Failed with $($_.Exception.Message)" -sev Error +} +# Final Score +$Result.Score = $ScoreDomain +$Result.ScorePercentage = [int](($Result.Score / $Result.MaximumScore)*100) +$Result.ScoreExplanation = ($ScoreExplanation) -join ", " + +# Final Write to Output +Log-request -API "DomainAnalyser" -tenant $tenant.tenant -message "DNS Analyser Finished For $($Result.Domain)" -sev Info +Write-Output $Result + diff --git a/DomainAnalyser_GetQueue/function.json b/DomainAnalyser_GetQueue/function.json new file mode 100644 index 000000000000..b31f1ad21352 --- /dev/null +++ b/DomainAnalyser_GetQueue/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "name", + "type": "activityTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/DomainAnalyser_GetQueue/run.ps1 b/DomainAnalyser_GetQueue/run.ps1 new file mode 100644 index 000000000000..175276caef7d --- /dev/null +++ b/DomainAnalyser_GetQueue/run.ps1 @@ -0,0 +1,28 @@ +param($name) + +$Tenants = Get-Tenants + +$object = foreach ($Tenant in $Tenants) { + # Get Domains to Lookup + try { + $Domains = New-GraphGetRequest -uri 'https://graph.microsoft.com/v1.0/domains' -tenantid $Tenant.defaultDomainName | Where-Object { ($_.id -notlike '*.onmicrosoft.com') -and ($_.supportedServices -contains 'Email') } + foreach ($d in $domains) { + [PSCustomObject]@{ + Tenant = $Tenant.defaultDomainName + Domain = $d.id + AuthenticationType = $d.authenticationType + IsAdminManaged = $d.isAdminManaged + IsDefault = $d.isDefault + IsInitial = $d.isInitial + IsRoot = $d.isRoot + IsVerified = $d.isVerified + SupportedServices = $d.supportedServices + } + } + } + catch { + Log-request -API 'DomainAnalyser' -tenant $tenant.defaultDomainName -message "DNS Analyser GraphGetRequest Exception: $($_.Exception.Message)" -sev Error + } +} + +$object \ No newline at end of file diff --git a/AccessChecks/function.json b/DomainAnalyser_List/function.json similarity index 100% rename from AccessChecks/function.json rename to DomainAnalyser_List/function.json diff --git a/NotifyTeams/run.ps1 b/DomainAnalyser_List/run.ps1 similarity index 53% rename from NotifyTeams/run.ps1 rename to DomainAnalyser_List/run.ps1 index e1a20af4be1a..4645ba4e5021 100644 --- a/NotifyTeams/run.ps1 +++ b/DomainAnalyser_List/run.ps1 @@ -10,18 +10,15 @@ Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -messa # Write to the Azure Functions log stream. Write-Host "PowerShell HTTP trigger function processed a request." -# Interact with query parameters or the body of the request. -$TenantFilter = $Request.Query.TenantFilter -if ($TenantFilter) { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999" -tenantid $TenantFilter -} -else { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999" -} +# Get all the things +$UnfilteredResults = Get-ChildItem ".\Cache_DomainAnalyser\*.json" | ForEach-Object { Get-Content $_.FullName | Out-String | ConvertFrom-Json } +# Need to apply exclusion logic +$Skiplist = Get-Content "ExcludedTenants" | ConvertFrom-Csv -Delimiter "|" -Header "Name", "User", "Date" +$Results = $UnfilteredResults | ForEach-Object { $_.GUID = $_.GUID -replace '[^a-zA-Z-]', ''; $_ } | Where-Object { $_.Tenant -notin $Skiplist.Name } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $GraphRequest + Body = @($Results) }) \ No newline at end of file diff --git a/DomainAnalyser_Orchestration/function.json b/DomainAnalyser_Orchestration/function.json new file mode 100644 index 000000000000..7326b39c184d --- /dev/null +++ b/DomainAnalyser_Orchestration/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "Context", + "type": "orchestrationTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/DomainAnalyser_Orchestration/run.ps1 b/DomainAnalyser_Orchestration/run.ps1 new file mode 100644 index 000000000000..04aa5b8758b8 --- /dev/null +++ b/DomainAnalyser_Orchestration/run.ps1 @@ -0,0 +1,21 @@ +param($Context) + +New-Item "Cache_DomainAnalyser" -ItemType Directory -ErrorAction SilentlyContinue +New-Item "Cache_DomainAnalyser\CurrentlyRunning.txt" -ItemType File -Force +$Batch = (Invoke-ActivityFunction -FunctionName 'DomainAnalyser_GetQueue' -Input 'LetsGo') +$ParallelTasks = foreach ($Item in $Batch) { + Invoke-DurableActivity -FunctionName "DomainAnalyser_All" -Input $item -NoWait +} + +$Outputs = Wait-ActivityFunction -Task $ParallelTasks +Log-request -API "DomainAnalyser" -tenant $tenant -message "Outputs found count = $($Outputs.count)" -sev Info + +foreach ($item in $Outputs) { + Write-Host $Item | Out-String + $Object = $Item | ConvertTo-Json + + Set-Content "Cache_DomainAnalyser\$($item.domain).DomainAnalysis.json" -Value $Object -Force +} + +Log-request -API "DomainAnalyser" -tenant $tenant -message "Domain Analyser has Finished" -sev Info +Remove-Item "Cache_DomainAnalyser\CurrentlyRunning.txt" -Force \ No newline at end of file diff --git a/DomainAnalyser_OrchestrationStarter/function.json b/DomainAnalyser_OrchestrationStarter/function.json new file mode 100644 index 000000000000..14c44f4f0217 --- /dev/null +++ b/DomainAnalyser_OrchestrationStarter/function.json @@ -0,0 +1,24 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "name": "Request", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "post", + "get" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" + } + ] +} diff --git a/DomainAnalyser_OrchestrationStarter/run.ps1 b/DomainAnalyser_OrchestrationStarter/run.ps1 new file mode 100644 index 000000000000..cbab5a8092db --- /dev/null +++ b/DomainAnalyser_OrchestrationStarter/run.ps1 @@ -0,0 +1,22 @@ +using namespace System.Net + +param($Request, $TriggerMetadata) +$CurrentlyRunning = Get-Item "Cache_DomainAnalyser\CurrentlyRunning.txt" -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).AddHours(-24) +if ($CurrentlyRunning) { + $Results = [pscustomobject]@{"Results" = "Already running. Please wait for the current instance to finish" } + Log-request -API "DomainAnalyser" -message "Attempted to start domain analysis but an instance was already running." -sev Info +} +else { + $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' + Write-Host "Started orchestration with ID = '$InstanceId'" + $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId + Log-request -API "DomainAnalyser" -message "Started retrieving domain information" -sev Info + $Results = [pscustomobject]@{"Results" = "Started running analysis" } +} +Write-Host ($Orchestrator | ConvertTo-Json) + + +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $results + }) \ No newline at end of file diff --git a/Domain_OrchestrationStarterTimer/function.json b/Domain_OrchestrationStarterTimer/function.json new file mode 100644 index 000000000000..f5df3587c4c8 --- /dev/null +++ b/Domain_OrchestrationStarterTimer/function.json @@ -0,0 +1,15 @@ +{ + "bindings": [ + { + "name": "Timer", + "type": "timerTrigger", + "direction": "in", + "schedule": "0 30 4 * * *" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/Domain_OrchestrationStarterTimer/run.ps1 b/Domain_OrchestrationStarterTimer/run.ps1 new file mode 100644 index 000000000000..4662cff15a12 --- /dev/null +++ b/Domain_OrchestrationStarterTimer/run.ps1 @@ -0,0 +1,15 @@ +param($Timer) + +$CurrentlyRunning = Get-Item "Cache_DomainAnalyser\CurrentlyRunning.txt" -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).AddHours(-24) +if ($CurrentlyRunning) { + $Results = [pscustomobject]@{"Results" = "Already running. Please wait for the current instance to finish" } + Log-request -API "DomainAnalyser" -message "Attempted to start analysis but an instance was already running." -sev Info +} +else { + $InstanceId = Start-NewOrchestration -FunctionName 'DomainAnalyser_Orchestration' + Write-Host "Started orchestration with ID = '$InstanceId'" + $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId + Log-request -API "DomainAnalyser" -message "Starting Domain Analyser" -sev Info + $Results = [pscustomobject]@{"Results" = "Starting Domain Analyser" } +} +Write-Host ($Orchestrator | ConvertTo-Json) diff --git a/EditGroup/run.ps1 b/EditGroup/run.ps1 index d3bbfecc12b8..f0a9ddeea00d 100644 --- a/EditGroup/run.ps1 +++ b/EditGroup/run.ps1 @@ -26,7 +26,7 @@ try { } catch { Log-Request -user $request.headers.'x-ms-client-principal' -message "Add member API failed. $($_.Exception.Message)" -Sev "Error" - $body = $results.add("Succesfully added the users $AddMembers to $($userobj.Groupid) $($_.Exception.Message)") + $body = $results.add("Failed to add $AddMembers to $($userobj.Groupid) $($_.Exception.Message)") } $RemoveMembers = ($userobj.Removemember).Split([Environment]::NewLine) @@ -37,17 +37,51 @@ try { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($userobj.groupid)/members/$($MemberInfo.id)/`$ref" -tenantid $Userobj.tenantid -type DELETE Log-Request -API $APINAME -tenant $Userobj.tenantid -user $request.headers.'x-ms-client-principal' -message "Removed $($MemberInfo.UserPrincipalname) from $($userobj.displayname) group" -Sev "Info" $body = $results.add("Success. Member $_ has been removed from $($userobj.Groupid)") + } + } +} +catch { + Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $Userobj.tenantid -message "Add member API failed. $($_.Exception.Message)" -Sev "Error" + $body = $results.add("Could not remove $RemoveMembers from $($userobj.Groupid). $($_.Exception.Message)") +} + +$AddOwners = ($userobj.Addowner).Split([Environment]::NewLine) +try { + if ($AddOwners) { + $AddOwners | ForEach-Object { + $ID = "https://graph.microsoft.com/beta/users/" + (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $Userobj.tenantid).id + Write-Host $ID + $AddOwner = New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($userobj.groupid)/owners/`$ref" -tenantid $Userobj.tenantid -type POST -body ('{"@odata.id": "' + $ID + '"}') + Log-Request -API $APINAME -tenant $Userobj.tenantid -user $request.headers.'x-ms-client-principal' -message "Added owner $_ to $($userobj.displayname) group" -Sev "Info" + $body = $results.add("Success. $_ has been added") + } - - + } } +catch { + Log-Request -user $request.headers.'x-ms-client-principal' -message "Add member API failed. $($_.Exception.Message)" -Sev "Error" + $body = $results.add("Failed to add $AddMembers to $($userobj.Groupid) $($_.Exception.Message)") +} + +$RemoveOwners = ($userobj.RemoveOwner).Split([Environment]::NewLine) +try { + if ($RemoveOwners) { + $RemoveOwners | ForEach-Object { + $MemberInfo = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($_)" -tenantid $Userobj.tenantid) + New-GraphPostRequest -uri "https://graph.microsoft.com/beta/groups/$($userobj.groupid)/owners/$($MemberInfo.id)/`$ref" -tenantid $Userobj.tenantid -type DELETE + Log-Request -API $APINAME -tenant $Userobj.tenantid -user $request.headers.'x-ms-client-principal' -message "Removed $($MemberInfo.UserPrincipalname) from $($userobj.displayname) group" -Sev "Info" + $body = $results.add("Success. Member $_ has been removed from $($userobj.Groupid)") + } + } +} catch { Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -tenant $Userobj.tenantid -message "Add member API failed. $($_.Exception.Message)" -Sev "Error" $body = $results.add("Could not remove $RemoveMembers from $($userobj.Groupid). $($_.Exception.Message)") } + $body = @{"Results" = ($results -join "
") } # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/EditUser/run.ps1 b/EditUser/run.ps1 index f30c2a148094..eb53a993740f 100644 --- a/EditUser/run.ps1 +++ b/EditUser/run.ps1 @@ -74,21 +74,21 @@ catch { $results.add( "Succesfully edit user. The password is $password. We've failed to assign the license. $($_.Exception.Message)") } -#Add aliasses, removal currently not supported. +#Add Aliases, removal currently not supported. try { - if ($aliasses) { - foreach ($Alias in $aliasses) { + if ($Aliases) { + foreach ($Alias in $Aliases) { New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userobj.Userid)" -tenantid $Userobj.tenantid -type "patch" -body "{`"mail`": `"$Alias`"}" -verbose } New-GraphPostRequest -uri "https://graph.microsoft.com/beta/users/$($userobj.Userid)" -tenantid $Userobj.tenantid -type "patch" -body "{`"mail`": `"$UserprincipalName`"}" -verbose - Log-Request -API $APINAME -tenant ($UserObj.tenantid) -user $request.headers.'x-ms-client-principal' -message "Added aliasses to $($userobj.displayname) license $($licences)" -Sev "Info" + Log-Request -API $APINAME -tenant ($UserObj.tenantid) -user $request.headers.'x-ms-client-principal' -message "Added Aliases to $($userobj.displayname) license $($licences)" -Sev "Info" $results.add( "Success. User has been edited") } } catch { Log-Request -API $APINAME -tenant ($UserObj.tenantid) -user $request.headers.'x-ms-client-principal' -message "Alias API failed. $($_.Exception.Message)" -Sev "Error" - $results.add( "Succesfully edited user. The password is $password. We've failed to create the aliasses: $($_.Exception.Message)") + $results.add( "Succesfully edited user. The password is $password. We've failed to create the Aliases: $($_.Exception.Message)") } if ($Request.body.CopyFrom -ne "") { diff --git a/AssignApp/function.json b/ExecAccessChecks/function.json similarity index 100% rename from AssignApp/function.json rename to ExecAccessChecks/function.json diff --git a/AccessChecks/run.ps1 b/ExecAccessChecks/run.ps1 similarity index 74% rename from AccessChecks/run.ps1 rename to ExecAccessChecks/run.ps1 index 168aa36e569f..04a01ed1ae21 100644 --- a/AccessChecks/run.ps1 +++ b/ExecAccessChecks/run.ps1 @@ -13,7 +13,7 @@ if ($Request.query.Permissions -eq "true") { Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Started permissions check" -Sev "Debug" $Results = try { $ExpectedPermissions = @( - "Application.Read.All", "Application.ReadWrite.All", "AuditLog.Read.All", "Channel.Create", "Channel.Delete.All", "Channel.ReadBasic.All", "ChannelMember.Read.All", "ChannelMember.ReadWrite.All", "ChannelMessage.Delete", "ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "ChannelSettings.Read.All", "ChannelSettings.ReadWrite.All", "ConsentRequest.Read.All", "Device.Command", "Device.Read", "Device.Read.All", "DeviceManagementApps.ReadWrite.All", "DeviceManagementConfiguration.ReadWrite.All", "DeviceManagementManagedDevices.ReadWrite.All", "DeviceManagementRBAC.ReadWrite.All", "DeviceManagementServiceConfig.ReadWrite.All", "Directory.AccessAsUser.All", "Group.ReadWrite.All", "GroupMember.ReadWrite.All", "Mail.Send", "Mail.Send.Shared", "Member.Read.Hidden", "Organization.ReadWrite.All", "Policy.Read.All", "Policy.ReadWrite.AuthenticationFlows", "Policy.ReadWrite.AuthenticationMethod", "Policy.ReadWrite.Authorization", "Policy.ReadWrite.ConsentRequest", "Policy.ReadWrite.DeviceConfiguration", "PrivilegedAccess.Read.AzureResources", "PrivilegedAccess.ReadWrite.AzureResources", "Reports.Read.All", "RoleManagement.ReadWrite.Directory", "SecurityActions.ReadWrite.All", "SecurityEvents.ReadWrite.All", "ServiceHealth.Read.All", "ServiceMessage.Read.All", "Sites.ReadWrite.All", "Team.Create", "Team.ReadBasic.All", "TeamMember.ReadWrite.All", "TeamMember.ReadWriteNonOwnerRole.All", "TeamsActivity.Read", "TeamsActivity.Send", "TeamsApp.Read", "TeamsApp.Read.All", "TeamsApp.ReadWrite", "TeamsApp.ReadWrite.All", "TeamsAppInstallation.ReadForChat", "TeamsAppInstallation.ReadForTeam", "TeamsAppInstallation.ReadForUser", "TeamsAppInstallation.ReadWriteForChat", "TeamsAppInstallation.ReadWriteForTeam", "TeamsAppInstallation.ReadWriteForUser", "TeamsAppInstallation.ReadWriteSelfForChat", "TeamsAppInstallation.ReadWriteSelfForTeam", "TeamsAppInstallation.ReadWriteSelfForUser", "TeamSettings.Read.All", "TeamSettings.ReadWrite.All", "TeamsTab.Create", "TeamsTab.Read.All", "TeamsTab.ReadWrite.All", "TeamsTab.ReadWriteForChat", "TeamsTab.ReadWriteForTeam", "TeamsTab.ReadWriteForUser", "ThreatAssessment.ReadWrite.All", "UnifiedGroupMember.Read.AsGuest", "User.ManageIdentities.All", "User.Read", "User.ReadWrite.All", "UserAuthenticationMethod.Read.All", "UserAuthenticationMethod.ReadWrite", "UserAuthenticationMethod.ReadWrite.All" + "Application.Read.All", "Application.ReadWrite.All", "AuditLog.Read.All", "Channel.Create", "Channel.Delete.All", "Channel.ReadBasic.All", "ChannelMember.Read.All", "ChannelMember.ReadWrite.All", "ChannelMessage.Delete", "ChannelMessage.Edit", "ChannelMessage.Read.All", "ChannelMessage.Send", "ChannelSettings.Read.All", "ChannelSettings.ReadWrite.All", "ConsentRequest.Read.All", "Device.Command", "Device.Read", "Device.Read.All", "DeviceManagementApps.ReadWrite.All", "DeviceManagementConfiguration.ReadWrite.All", "DeviceManagementManagedDevices.ReadWrite.All", "DeviceManagementRBAC.ReadWrite.All", "DeviceManagementServiceConfig.ReadWrite.All", "Directory.AccessAsUser.All", "Domain.Read.All", "Group.ReadWrite.All", "GroupMember.ReadWrite.All", "Mail.Send", "Mail.Send.Shared", "Member.Read.Hidden", "Organization.ReadWrite.All", "Policy.Read.All", "Policy.ReadWrite.AuthenticationFlows", "Policy.ReadWrite.AuthenticationMethod", "Policy.ReadWrite.Authorization", "Policy.ReadWrite.ConsentRequest", "Policy.ReadWrite.DeviceConfiguration", "PrivilegedAccess.Read.AzureResources", "PrivilegedAccess.ReadWrite.AzureResources", "Reports.Read.All", "RoleManagement.ReadWrite.Directory", "SecurityActions.ReadWrite.All", "SecurityEvents.ReadWrite.All", "ServiceHealth.Read.All", "ServiceMessage.Read.All", "Sites.ReadWrite.All", "Team.Create", "Team.ReadBasic.All", "TeamMember.ReadWrite.All", "TeamMember.ReadWriteNonOwnerRole.All", "TeamsActivity.Read", "TeamsActivity.Send", "TeamsApp.Read", "TeamsApp.Read.All", "TeamsApp.ReadWrite", "TeamsApp.ReadWrite.All", "TeamsAppInstallation.ReadForChat", "TeamsAppInstallation.ReadForTeam", "TeamsAppInstallation.ReadForUser", "TeamsAppInstallation.ReadWriteForChat", "TeamsAppInstallation.ReadWriteForTeam", "TeamsAppInstallation.ReadWriteForUser", "TeamsAppInstallation.ReadWriteSelfForChat", "TeamsAppInstallation.ReadWriteSelfForTeam", "TeamsAppInstallation.ReadWriteSelfForUser", "TeamSettings.Read.All", "TeamSettings.ReadWrite.All", "TeamsTab.Create", "TeamsTab.Read.All", "TeamsTab.ReadWrite.All", "TeamsTab.ReadWriteForChat", "TeamsTab.ReadWriteForTeam", "TeamsTab.ReadWriteForUser", "ThreatAssessment.ReadWrite.All", "UnifiedGroupMember.Read.AsGuest", "User.ManageIdentities.All", "User.Read", "User.ReadWrite.All", "UserAuthenticationMethod.Read.All", "UserAuthenticationMethod.ReadWrite", "UserAuthenticationMethod.ReadWrite.All" ) $GraphPermissions = ((Get-GraphToken -returnRefresh $true).scope).split(' ') -replace "https://graph.microsoft.com/", "" | Where-Object { $_ -notin @("email", "openid", "profile", ".default") } $MissingPermissions = $ExpectedPermissions | Where-Object { $_ -notin $GraphPermissions } diff --git a/ConverttoSharedMailbox/function.json b/ExecAssignApp/function.json similarity index 100% rename from ConverttoSharedMailbox/function.json rename to ExecAssignApp/function.json diff --git a/AssignApp/run.ps1 b/ExecAssignApp/run.ps1 similarity index 100% rename from AssignApp/run.ps1 rename to ExecAssignApp/run.ps1 diff --git a/DisableUser/function.json b/ExecBackendURLs/function.json similarity index 100% rename from DisableUser/function.json rename to ExecBackendURLs/function.json diff --git a/ExecBackendURLs/run.ps1 b/ExecBackendURLs/run.ps1 new file mode 100644 index 000000000000..29544b2df611 --- /dev/null +++ b/ExecBackendURLs/run.ps1 @@ -0,0 +1,37 @@ +using namespace System.Net + +# Input bindings are passed in via param block. +param($Request, $TriggerMetadata) + +$APIName = $TriggerMetadata.FunctionName +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" + +$Subscription = ($ENV:WEBSITE_OWNER_NAME).split('+') | Select-Object -First 1 +$SWAName = $ENV:Website_SITE_NAME -replace "cipp", "CIPP-SWA-" +# Write to the Azure Functions log stream. +Write-Host "PowerShell HTTP trigger function processed a request." + +$results = @" +

Resource Group



+ +Resource group
+Keyvault (Password storage)
+

Function app



+Function Application (Overview)
+Function Application (Configuration)
+Function Application (Deployment Center)
+

Static Web App



+Static Web App (Custom Domains)
+Static Web App (Role Management)
+ + +"@ + + +$body = @{Results = $Results } + +# Associate values to output bindings by calling 'Push-OutputBinding'. +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [httpstatusCode]::OK + Body = $body + }) diff --git a/ExcludeTenant/function.json b/ExecConverttoSharedMailbox/function.json similarity index 100% rename from ExcludeTenant/function.json rename to ExecConverttoSharedMailbox/function.json diff --git a/ConverttoSharedMailbox/run.ps1 b/ExecConverttoSharedMailbox/run.ps1 similarity index 100% rename from ConverttoSharedMailbox/run.ps1 rename to ExecConverttoSharedMailbox/run.ps1 diff --git a/Logs/function.json b/ExecDisableUser/function.json similarity index 100% rename from Logs/function.json rename to ExecDisableUser/function.json diff --git a/DisableUser/run.ps1 b/ExecDisableUser/run.ps1 similarity index 100% rename from DisableUser/run.ps1 rename to ExecDisableUser/run.ps1 diff --git a/NotifyEmail/function.json b/ExecExcludeTenant/function.json similarity index 100% rename from NotifyEmail/function.json rename to ExecExcludeTenant/function.json diff --git a/ExcludeTenant/run.ps1 b/ExecExcludeTenant/run.ps1 similarity index 96% rename from ExcludeTenant/run.ps1 rename to ExecExcludeTenant/run.ps1 index 1cc65a15adf0..184b01445d87 100644 --- a/ExcludeTenant/run.ps1 +++ b/ExecExcludeTenant/run.ps1 @@ -23,7 +23,8 @@ try { $name = $Request.Query.TenantFilter if ($Request.Query.AddExclusion) { Add-Content -Value "$($name)|$($username)|$($date)" -Path "ExcludedTenants" - Log-Request -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer $($name)" -Sev "Info" + Remove-CIPPCache + Log-Request -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Added exclusion for customer $($name)" -Sev "Info" $body = [pscustomobject]@{"Results" = "Success. We've added $name to the excluded tenants." } } @@ -31,15 +32,20 @@ try { $Content = [System.IO.File]::ReadAllLines("ExcludedTenants") $Content = $Content -replace $name, '' $Content | Set-Content -Path "ExcludedTenants" + Remove-CIPPCache Log-Request -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Removed exclusion for customer $($name)" -Sev "Info" $body = [pscustomobject]@{"Results" = "Success. We've removed $name from the excluded tenants." } } + + } catch { Log-Request -API $APINAME -tenant $($name) -user $request.headers.'x-ms-client-principal' -message "Exclusion API failed. $($_.Exception.Message)" -Sev "Error" $body = [pscustomobject]@{"Results" = "Failed. $($_.Exception.Message)" } } + + # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK diff --git a/NotifyTeams/function.json b/ExecOffboardUser/function.json similarity index 100% rename from NotifyTeams/function.json rename to ExecOffboardUser/function.json diff --git a/OffboardUser/run.ps1 b/ExecOffboardUser/run.ps1 similarity index 100% rename from OffboardUser/run.ps1 rename to ExecOffboardUser/run.ps1 diff --git a/OffboardUser/function.json b/ExecResetPass/function.json similarity index 100% rename from OffboardUser/function.json rename to ExecResetPass/function.json diff --git a/ResetPass/run.ps1 b/ExecResetPass/run.ps1 similarity index 100% rename from ResetPass/run.ps1 rename to ExecResetPass/run.ps1 diff --git a/ResetPass/function.json b/ExecSendPush/function.json similarity index 100% rename from ResetPass/function.json rename to ExecSendPush/function.json diff --git a/ExecSendPush/run.ps1 b/ExecSendPush/run.ps1 new file mode 100644 index 000000000000..70e031db2a7b --- /dev/null +++ b/ExecSendPush/run.ps1 @@ -0,0 +1,114 @@ +using namespace System.Net + +# Input bindings are passed in via param block. +param($Request, $TriggerMetadata) + +$APIName = $TriggerMetadata.FunctionName +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" + +$TenantFilter = $Request.Query.TenantFilter +$UserEmail = $Request.Query.UserEmail +$MFAAppID = '981f26a1-7f43-403b-a875-f8b09b8cd720' + +# Function to keep trying to get the access token while we wait for MS to actually set the temp password +function get-clientaccess { + param( + $uri, + $body, + $count = 1 + ) + try { + $ClientToken = Invoke-RestMethod -Method post -Uri $uri -Body $body -ea stop + } catch { + if ($count -lt 20) { + + $count++ + Start-Sleep 1 + $ClientToken = get-clientaccess -uri $uri -body $body -count $count + } else { + Throw "Could not get Client Token: $_" + } + } + return $ClientToken +} + + +# Get all service principals +$SPResult = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/servicePrincipals?`$top=999&`$select=id,appId" -tenantid $TenantFilter + +# Check if we have one for the MFA App +$SPID = ($SPResult | where-object { $_.appId -eq $MFAAppID }).id + +# Create a serivce principal if needed +if (!$SPID) { + + $SPBody = [pscustomobject]@{ + appId = $MFAAppID + } + $SPID = (New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals" -tenantid $TenantFilter -type POST -body $SPBody -verbose).id +} + + +$PassReqBody = @{ + "passwordCredential" = @{ + "displayName" = "MFA Temporary Password" + "endDateTime" = $(((Get-Date).addminutes(5))) + "startDateTime" = $((Get-Date).addminutes(-5)) + } +} | ConvertTo-Json -depth 5 + +$TempPass = (New-GraphPostRequest -uri "https://graph.microsoft.com/v1.0/servicePrincipals/$SPID/addPassword" -tenantid $TenantFilter -type POST -body $PassReqBody -verbose).secretText + +# Give it a chance to apply +#Start-Sleep 5 + +# Generate the XML for the push request +$XML = @" + +1.0 +$UserEmail +en-usOverrideVoiceOtpfalse69ff05bf-eb61-47f7-a70e-e7d77b6d47d0 +truetrueradiusUNKNOWN: +"@ + +# Request to get client token +$body = @{ + 'resource' = 'https://adnotifications.windowsazure.com/StrongAuthenticationService.svc/Connector' + 'client_id' = $MFAAppID + 'client_secret' = $TempPass + 'grant_type' = "client_credentials" + 'scope' = "openid" +} + +# Attempt to get a token using the temp password +$ClientUri = "https://login.microsoftonline.com/$TenantFilter/oauth2/token" +try { + $ClientToken = get-clientaccess -Uri $ClientUri -Body $body +} catch { + $Body = "Failed to create temporary password" +} + +# If we got a token send a push +if ($ClientToken) { + + $ClientHeaders = @{ "Authorization" = "Bearer $($ClientToken.access_token)" } + + $obj = Invoke-RestMethod -uri 'https://adnotifications.windowsazure.com/StrongAuthenticationService.svc/Connector//BeginTwoWayAuthentication' -Method POST -Headers $ClientHeaders -Body $XML -ContentType 'application/xml' + + if ($obj.BeginTwoWayAuthenticationResponse.result) { + $Body = "
  Received an MFA confirmation: $($obj.BeginTwoWayAuthenticationResponse.result.value | Out-String)
" + } + if ($obj.BeginTwoWayAuthenticationResponse.AuthenticationResult -ne $true) { + $Body = "
  Authentication Failed! Does the user have Push/Phone call MFA configured? Errorcode: $($obj.BeginTwoWayAuthenticationResponse.result.value | out-string)
" + } + +} + +$Results = [pscustomobject]@{"Results" = $Body } +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Sent push request to $UserEmail - Result: $($obj.BeginTwoWayAuthenticationResponse.result.value | out-string)" -Sev "Info" + +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Results + }) + diff --git a/GetVersion/run.ps1 b/GetVersion/run.ps1 index 3336e63cfbc3..d6db3204d09b 100644 --- a/GetVersion/run.ps1 +++ b/GetVersion/run.ps1 @@ -6,10 +6,21 @@ param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" -$Version = Get-Content "version_latest.txt" +$APIVersion = Get-Content "version_latest.txt" | Out-String +$CIPPVersion = $request.query.localversion +$RemoteAPIVersion = Invoke-RestMethod -Uri "https://raw.githubusercontent.com/KelvinTegelaar/CIPP-API/master/version_latest.txt" +$RemoteCIPPVersion = Invoke-RestMethod -Uri "https://raw.githubusercontent.com/KelvinTegelaar/CIPP/master/version_latest.txt" + +$version = [PSCustomObject]@{ + LocalCIPPVersion = $CIPPVersion + RemoteCIPPVersion = $RemoteCIPPVersion + LocalCIPPAPIVersion = $APIVersion + RemoteCIPPAPIVersion = $RemoteAPIVersion + OutOfDateCIPP = ([version]$RemoteCIPPVersion -ge [version]$CIPPVersion) + OutOfDateCIPPAPI = ([version]$RemoteAPIVersion -ge [version]$APIVersion) +} # Write to the Azure Functions log stream. -Write-Host "Version is $($Version)" # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ diff --git a/GraphHelper.psm1 b/GraphHelper.psm1 index 25dd8b369602..3db015831e1b 100644 --- a/GraphHelper.psm1 +++ b/GraphHelper.psm1 @@ -47,13 +47,8 @@ function Log-Request ($message, $tenant, $API, $user, $sev) { $LogMutex.ReleaseMutex() } -function New-GraphGetRequest ($uri, $tenantid, $scope, $AsApp) { - $TenantList = Get-Content 'Tenants.cache.json' -ErrorAction SilentlyContinue | ConvertFrom-Json - $Skiplist = Get-Content "ExcludedTenants" | ConvertFrom-Csv -Delimiter "|" -Header "Name", "User", "Date" +function New-GraphGetRequest ($uri, $tenantid, $scope, $AsApp, $noPagination) { - if ($tenantid -ne $null -and $tenantid -in $($Skiplist.name)) { - return "Not allowed. Tenant is in exclusion list." - } if ($scope -eq "ExchangeOnline") { $Headers = Get-GraphToken -AppID 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -RefreshToken $ENV:ExchangeRefreshToken -Scope 'https://outlook.office365.com/.default' -Tenantid $tenantid } @@ -62,13 +57,13 @@ function New-GraphGetRequest ($uri, $tenantid, $scope, $AsApp) { } Write-Verbose "Using $($uri) as url" $nextURL = $uri - #not a fan of this, have to reconsider and change. Seperate function? - if ($tenantid -in $tenantlist.defaultdomainname -or $uri -like "https://graph.microsoft.com/beta/contracts?`$top=999" -or $uri -like "*/customers/*") { + + if ((Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { $ReturnedData = do { try { $Data = (Invoke-RestMethod -Uri $nextURL -Method GET -Headers $headers -ContentType "application/json; charset=utf-8") if ($data.value) { $data.value } else { ($Data) } - $nextURL = $data.'@odata.nextLink' + if ($noPagination) { $nextURL = $null } else { $nextURL = $data.'@odata.nextLink' } } catch { $Message = ($_.ErrorDetails.Message | ConvertFrom-Json).error.message @@ -84,19 +79,14 @@ function New-GraphGetRequest ($uri, $tenantid, $scope, $AsApp) { } function New-GraphPOSTRequest ($uri, $tenantid, $body, $type, $scope, $AsApp) { - $Skiplist = Get-Content "ExcludedTenants" | ConvertFrom-Csv -Delimiter "|" -Header "Name", "User", "Date" - $TenantList = Get-Content 'Tenants.cache.json' -ErrorAction SilentlyContinue | ConvertFrom-Json - if ($tenantid -ne $null -and $tenantid -in $($Skiplist.name)) { - return "Not allowed. Tenant is in exclusion list." - } $headers = Get-GraphToken -tenantid $tenantid -scope $scope -AsApp $asapp Write-Verbose "Using $($uri) as url" if (!$type) { $type = 'POST' } - #not a fan of this, have to reconsider and change. Seperate function? - if ($tenantid -in $tenantlist.defaultdomainname -or $uri -like "*/contracts*" -or $uri -like "*/customers/*") { + + if ((Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { try { $ReturnedData = (Invoke-RestMethod -Uri $($uri) -Method $TYPE -Body $body -Headers $headers -ContentType "application/json; charset=utf-8") } @@ -118,3 +108,151 @@ function convert-skuname($skuname, $skuID) { if ($skuID) { $ReturnedName = ($ConvertTable | Where-Object { $_.guid -eq $skuid } | Select-Object -Last 1).'Product_Display_Name' } if ($ReturnedName) { return $ReturnedName } else { return $skuname, $skuID } } + +function Get-ClassicAPIToken($tenantID, $Resource) { + $uri = "https://login.microsoftonline.com/$($TenantID)/oauth2/token" + $body = "resource=$Resource&grant_type=refresh_token&refresh_token=$($ENV:ExchangeRefreshToken)" + try { + $token = Invoke-RestMethod $uri -Body $body -ContentType "application/x-www-form-urlencoded" -ErrorAction SilentlyContinue -Method post + return $token + } + catch { + Write-Error "Failed to obtain Classic API Token for $Tenant - $_" + } +} + +function New-ClassicAPIGetRequest($TenantID, $Uri, $Method = 'GET', $Resource = 'https://admin.microsoft.com') { + $token = Get-ClassicAPIToken -Tenant $tenantID -Resource $Resource + + $NextURL = $Uri + + if ((Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { + $ReturnedData = do { + try { + $Data = Invoke-RestMethod -ContentType "application/json;charset=UTF-8" -Uri $NextURL -Method $Method -Headers @{ + Authorization = "Bearer $($token.access_token)"; + "x-ms-client-request-id" = [guid]::NewGuid().ToString(); + "x-ms-client-session-id" = [guid]::NewGuid().ToString() + 'x-ms-correlation-id' = [guid]::NewGuid() + 'X-Requested-With' = 'XMLHttpRequest' + } + $Data + if ($noPagination) { $nextURL = $null } else { $nextURL = $data.NextLink } + } + catch { + throw "Failed to make Classic Get Request $_" + } + } until ($null -eq $NextURL) + return $ReturnedData + } + else { + Write-Error "Not allowed. You cannot manage your own tenant or tenants not under your scope" + } +} + +function New-ClassicAPIPostRequest($TenantID, $Uri, $Method = 'POST', $Resource = 'https://admin.microsoft.com', $Body) { + + $token = Get-ClassicAPIToken -Tenant $tenantID -Resource $Resource + + if ((Get-AuthorisedRequest -Uri $uri -TenantID $tenantid)) { + try { + $ReturnedData = Invoke-RestMethod -ContentType "application/json;charset=UTF-8" -Uri $Uri -Method $Method -Body $Body -Headers @{ + Authorization = "Bearer $($token.access_token)"; + "x-ms-client-request-id" = [guid]::NewGuid().ToString(); + "x-ms-client-session-id" = [guid]::NewGuid().ToString() + 'x-ms-correlation-id' = [guid]::NewGuid() + 'X-Requested-With' = 'XMLHttpRequest' + } + + } + catch { + throw "Failed to make Classic Get Request $_" + } + return $ReturnedData + } + else { + Write-Error "Not allowed. You cannot manage your own tenant or tenants not under your scope" + } +} + +function Get-AuthorisedRequest($TenantID, $Uri) { + if ($uri -like "https://graph.microsoft.com/beta/contracts?`$top=999" -or $uri -like "*/customers/*") { + return $true + } + if ($TenantID -in (Get-Tenants).defaultdomainname) { + return $true + } + else { + return $false + } + +} + +function Get-Tenants { + param ( + [Parameter( ParameterSetName = 'Skip', Mandatory = $True )] + [switch]$SkipList, + [Parameter( ParameterSetName = 'Standard')] + [switch]$IncludeAll + + ) + + $cachefile = 'tenants.cache.json' + + if ((!$Script:SkipListCache -and !$Script:SkipListCacheEmpty) -or !$Script:IncludedTenantsCache) { + # We create the excluded tenants file. This is not set to force so will not overwrite + New-Item -ErrorAction SilentlyContinue -ItemType File -Path "ExcludedTenants" + $Script:SkipListCache = Get-Content "ExcludedTenants" | ConvertFrom-Csv -Delimiter "|" -Header "Name", "User", "Date" + if ($null -eq $Script:SkipListCache) { + $Script:SkipListCacheEmpty = $true + } + + # Load or refresh the cache if older than 24 hours + $Testfile = Get-Item $cachefile -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).Addhours(-24) + if ($Testfile) { + $Script:IncludedTenantsCache = Get-Content $cachefile -ErrorAction SilentlyContinue | ConvertFrom-Json + } + else { + $Script:IncludedTenantsCache = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/contracts?`$top=999" -tenantid $ENV:Tenantid) | Select-Object CustomerID, DefaultdomainName, DisplayName, domains | Where-Object -Property DefaultdomainName -NotIn $Script:SkipListCache.name + if ($Script:IncludedTenantsCache) { + $Script:IncludedTenantsCache | ConvertTo-Json | Out-File $cachefile + } + } + } + if ($SkipList) { + return $Script:SkipListCache + } + if ($IncludeAll) { + return (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/contracts?`$top=999" -tenantid $ENV:Tenantid) | Select-Object CustomerID, DefaultdomainName, DisplayName, domains + } + else { + return $Script:IncludedTenantsCache + } +} + +function Remove-CIPPCache { + Remove-Item 'tenants.cache.json' -Force + Get-ChildItem -Path "Cache_BestPracticeAnalyser" -Filter *.json | Remove-Item -Force -ErrorAction SilentlyContinue + Get-ChildItem -Path "Cache_DomainAnalyser" -Filter *.json | Remove-Item -Force -ErrorAction SilentlyContinue + $Script:SkipListCache = $Null + $Script:SkipListCacheEmpty = $Null + $Script:IncludedTenantsCache = $Null +} + +function New-ExoRequest ($tenantid, $cmdlet, $cmdParams) { + $Headers = Get-GraphToken -AppID 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -RefreshToken $ENV:ExchangeRefreshToken -Scope 'https://outlook.office365.com/.default' -Tenantid $tenantid + if ((Get-AuthorisedRequest -TenantID $tenantid)) { + $tenant = (get-tenants | Where-Object -Property defaultDomainName -EQ $tenantid).customerid + $ExoBody = @{ + CmdletInput = @{ + CmdletName = $cmdlet + Parameters = @{} + } + } | ConvertTo-Json + $ReturnedData = Invoke-RestMethod "https://outlook.office365.com/adminapi/beta/$($tenant)/InvokeCommand" -Method POST -Body $ExoBody -Headers $Headers -ContentType "application/json; charset=utf-8" + return $ReturnedData.value + } + else { + Write-Error "Not allowed. You cannot manage your own tenant or tenants not under your scope" + } +} \ No newline at end of file diff --git a/ListContacts/function.json b/ListContacts/function.json new file mode 100644 index 000000000000..306b0c51e560 --- /dev/null +++ b/ListContacts/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} \ No newline at end of file diff --git a/NotifyEmail/run.ps1 b/ListContacts/run.ps1 similarity index 53% rename from NotifyEmail/run.ps1 rename to ListContacts/run.ps1 index e1a20af4be1a..dd3db876f654 100644 --- a/NotifyEmail/run.ps1 +++ b/ListContacts/run.ps1 @@ -6,22 +6,26 @@ param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" +$selectlist = "id", "companyName","displayName","mail","onPremisesSyncEnabled","editURL" # Write to the Azure Functions log stream. Write-Host "PowerShell HTTP trigger function processed a request." + # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter -if ($TenantFilter) { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999" -tenantid $TenantFilter -} -else { - $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$top=999" -} +$ContactID = $Request.Query.ContactID +Write-Host "Tenant Filter: $TenantFilter" + +$GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/contacts/$($ContactID)?`$top=999&`$select=$($selectlist -join ',')" -tenantid $TenantFilter | Select-Object $selectlist | foreach-object { + $_.editURL = "https://outlook.office365.com/ecp/@$TenantFilter/UsersGroups/EditContact.aspx?exsvurl=1&realm=$($Env:TenantID)&mkt=en-US&id=$($_.id)" + $_ +} # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK - Body = $GraphRequest - }) \ No newline at end of file + Body = @($GraphRequest) + }) + diff --git a/ListGroups/run.ps1 b/ListGroups/run.ps1 index 9bcf877913a1..b83ce69622fc 100644 --- a/ListGroups/run.ps1 +++ b/ListGroups/run.ps1 @@ -13,19 +13,23 @@ Write-Host "PowerShell HTTP trigger function processed a request." # Interact with query parameters or the body of the request. $TenantFilter = $Request.Query.TenantFilter - $selectstring = "id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes" +$selectstring = "id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule,grouptypes" -if($Request.Query.GroupID){ -$groupid = $Request.query.groupid -$selectstring = "id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule" +if ($Request.Query.GroupID) { + $groupid = $Request.query.groupid + $selectstring = "id,createdDateTime,displayName,description,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule" } -if($Request.Query.members){ +if ($Request.Query.members) { $members = "members" $selectstring = "id,createdDateTime,displayName,description,hideFromOutlookClients,hideFromAddressLists,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule" } +if ($Request.Query.owners) { + $members = "owners" + $selectstring = "id,createdDateTime,displayName,description,hideFromOutlookClients,hideFromAddressLists,mail,mailEnabled,mailNickname,resourceProvisioningOptions,securityEnabled,visibility,organizationId,onPremisesSamAccountName,membershipRule" +} - $GraphRequest = New-GraphGetRequest -asApp $true -uri "https://graph.microsoft.com/beta/groups/$($GroupID)/$($members)?`$top=999&select=$selectstring" -tenantid $TenantFilter | select-object *,@{ Name = 'primDomain'; Expression = { $_.mail -split "@" | Select-Object -last 1 } } +$GraphRequest = New-GraphGetRequest -asApp $true -uri "https://graph.microsoft.com/beta/groups/$($GroupID)/$($members)?`$top=999&select=$selectstring" -tenantid $TenantFilter | Select-Object *, @{ Name = 'primDomain'; Expression = { $_.mail -split "@" | Select-Object -Last 1 } } # Associate values to output bindings by calling 'Push-OutputBinding'. diff --git a/ListLogs/function.json b/ListLogs/function.json new file mode 100644 index 000000000000..306b0c51e560 --- /dev/null +++ b/ListLogs/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} \ No newline at end of file diff --git a/Logs/run.ps1 b/ListLogs/run.ps1 similarity index 69% rename from Logs/run.ps1 rename to ListLogs/run.ps1 index 5322a5cb4a92..b265ad2df70b 100644 --- a/Logs/run.ps1 +++ b/ListLogs/run.ps1 @@ -7,12 +7,10 @@ $APIName = $TriggerMetadata.FunctionName Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" -$LogLevel = if ($Request.Query.LogLevel) { ($Request.query.Loglevel).split(',') } else { "Info", "Warn", "Error", "Critical" } -# Write to the Azure Functions log stream. -Write-Host "PowerShell HTTP trigger function processed a request." -# Interact with query parameters or the body of the request. +$LogLevel = if ($Request.Query.Severity) { ($Request.query.Severity).split(',') } else { "Info", "Warn", "Error", "Critical" } $date = if ($Request.Query.DateFilter) { $Request.query.DateFilter } else { (Get-Date).ToString('MMyyyy') } -$ReturnedLog = Get-Content "$($date).log" | ConvertFrom-Csv -Header "DateTime", "Tenant", "API", "Message", "User", "Severity" -Delimiter "|" | Where-Object -Property Severity -In $LogLevel +$username = if ($Request.Query.User) { $Request.Query.User } else { '*' } +$ReturnedLog = Get-Content "$($date).log" | ConvertFrom-Csv -Header "DateTime", "Tenant", "API", "Message", "User", "Severity" -Delimiter "|" | Where-Object { $_.Severity -In $LogLevel -and $_.user -like $username } if ($request.query.last) { $ReturnedLog = $ReturnedLog | Select-Object -Last $request.query.last diff --git a/ListTenants/run.ps1 b/ListTenants/run.ps1 index 55366c926784..aa30f8a7ee2d 100644 --- a/ListTenants/run.ps1 +++ b/ListTenants/run.ps1 @@ -6,16 +6,9 @@ param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" -# We create the excluded tenants file. This is not set to force so will not overwrite -New-Item -ErrorAction SilentlyContinue -ItemType File -Path "ExcludedTenants" - -# Set cache locations -$cachefile = 'tenants.cache.json' - # Clear Cache if ($request.Query.ClearCache -eq "true") { - Remove-Item $cachefile -Force - Get-ChildItem -Path "Cache_BestPracticeAnalyser" -Filter *.json | Remove-Item -Force -ErrorAction SilentlyContinue + Remove-CIPPCache $GraphRequest = [pscustomobject]@{"Results" = "Successfully completed request." } Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK @@ -24,30 +17,11 @@ if ($request.Query.ClearCache -eq "true") { exit } -# Get the list of tenants to skip, create the file first if it does not exist yet. -$Skiplist = (Get-Content ExcludedTenants -ErrorAction SilentlyContinue | ConvertFrom-Csv -Delimiter "|" -Header "name", "date", "user").name +$Body = Get-Tenants -# Get the tenant cache file where it is under 24 hours old. If it's over 24 hours old, re-create it -$Testfile = Get-Item $cachefile -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).Addhours(-24) -if ($Testfile) { - $GraphRequest = Get-Content $cachefile | ConvertFrom-Json - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $GraphRequest | Where-Object -Property DefaultdomainName -NotIn $Skiplist - }) - exit -} -else { - Write-Host "Grabbing all tenants via Graph API" -ForegroundColor Green - $GraphRequest = (New-GraphGetRequest -uri "https://graph.microsoft.com/beta/contracts?`$top=999" -tenantid $ENV:Tenantid) | Select-Object CustomerID, DefaultdomainName, DisplayName, domains | Where-Object -Property DefaultdomainName -NotIn $Skiplist - # Associate values to output bindings by calling 'Push-OutputBinding'. - Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ - StatusCode = [HttpStatusCode]::OK - Body = $GraphRequest - }) + +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $Body + }) - # Re have returned tenants now, but we still need to generate the cache file and save it - if ($GraphRequest) { - $GraphRequest | ConvertTo-Json | Out-File $cachefile - } -} diff --git a/ListUserConditionalAccessPolicies/function.json b/ListUserConditionalAccessPolicies/function.json new file mode 100644 index 000000000000..306b0c51e560 --- /dev/null +++ b/ListUserConditionalAccessPolicies/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} \ No newline at end of file diff --git a/ListUserConditionalAccessPolicies/run.ps1 b/ListUserConditionalAccessPolicies/run.ps1 new file mode 100644 index 000000000000..99112b307916 --- /dev/null +++ b/ListUserConditionalAccessPolicies/run.ps1 @@ -0,0 +1,451 @@ +using namespace System.Net + +# Input bindings are passed in via param block. +param($Request, $TriggerMetadata) + +$APIName = $TriggerMetadata.FunctionName +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" + + +function Get-LocationNameFromId { + [CmdletBinding()] + param ( + [Parameter()] + $ID, + + [Parameter(Mandatory = $true)] + $Locations + ) + if ($id -eq 'All') { + return 'All' + } + $DisplayName = $Locations | ? { $_.id -eq $ID } | Select -ExpandProperty DisplayName + if ([string]::IsNullOrEmpty($displayName)) { + return "" + } else { + return $DisplayName + } +} + +function Get-RoleNameFromId { + [CmdletBinding()] + param ( + [Parameter()] + $ID, + + [Parameter(Mandatory = $true)] + $RoleDefinitions + ) + if ($id -eq 'All') { + return 'All' + } + $DisplayName = $RoleDefinitions | ? { $_.id -eq $ID } | Select -ExpandProperty DisplayName + if ([string]::IsNullOrEmpty($displayName)) { + return "" + } else { + return $DisplayName + } +} + +function Get-UserNameFromId { + [CmdletBinding()] + param ( + [Parameter()] + $ID, + + [Parameter(Mandatory = $true)] + $Users + ) + if ($id -eq 'All') { + return 'All' + } + $DisplayName = $Users | ? { $_.id -eq $ID } | Select -ExpandProperty DisplayName + if ([string]::IsNullOrEmpty($displayName)) { + return "" + } else { + return $DisplayName + } +} + +function Get-GroupNameFromId { + param ( + [Parameter()] + $ID, + + [Parameter(Mandatory = $true)] + $Groups + ) + if ($id -eq 'All') { + return 'All' + } + $DisplayName = $Groups | ? { $_.id -eq $ID } | Select -ExpandProperty DisplayName + if ([string]::IsNullOrEmpty($displayName)) { + return "No Data" + } else { + return $DisplayName + } +} + +function Get-ApplicationNameFromId { + [CmdletBinding()] + param ( + [Parameter()] + $ID, + + [Parameter(Mandatory = $true)] + $Applications + ) + if ($id -eq 'All') { + return 'All' + } + switch ($id) { + 00000004-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Lync' } + 00000006-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Office365Portal' } + 00000003-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.SharePoint ' } + 00000005-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.Workflow' } + 00000009-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.AnalysisServices' } + 00000002-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.Exchange' } + 00000007-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.ExchangeOnlineProtection' } + 00000002-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.ActiveDirectory' } + 8fca0a66-c008-4564-a876-ab3ae0fd5cff { $return = 'Microsoft.SMIT' } + 0000000b-0000-0000-c000-000000000000 { $return = 'Microsoft.SellerDashboard' } + 0000000f-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.GraphExplorer' } + 0000000c-0000-0000-c000-000000000000 { $return = 'Microsoft App Access Panel' } + 00000013-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.Portal' } + 00000010-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.GraphStore' } + 93ee9413-cf4c-4d4e-814b-a91ff20a01bd { $return = 'Workflow' } + aa9ecb1e-fd53-4aaa-a8fe-7a54de2c1334 { $return = 'Microsoft.Office365.Configure' } + 797f4846-ba00-4fd7-ba43-dac1f8f63013 { $return = 'Windows Azure Service Management API' } + 00000005-0000-0ff1-ce00-000000000000 { $return = 'Microsoft.YammerEnterprise' } + 601d4e27-7bb3-4dee-8199-90d47d527e1c { $return = 'Microsoft.Office365.ChangeManagement' } + 6f82282e-0070-4e78-bc23-e6320c5fa7de { $return = 'Microsoft.DiscoveryService' } + 0f698dd4-f011-4d23-a33e-b36416dcb1e6 { $return = 'Microsoft.OfficeClientService' } + 67e3df25-268a-4324-a550-0de1c7f97287 { $return = 'Microsoft.OfficeWebAppsService' } + ab27a73e-a3ba-4e43-8360-8bcc717114d8 { $return = 'Microsoft.OfficeModernCalendar' } + aedca418-a84d-430d-ab84-0b1ef06f318f { $return = 'Workflow' } + 595d87a1-277b-4c0a-aa7f-44f8a068eafc { $return = 'Microsoft.SupportTicketSubmission' } + e3583ad2-c781-4224-9b91-ad15a8179ba0 { $return = 'Microsoft.ExtensibleRealUserMonitoring' } + b645896d-566e-447e-8f7f-e2e663b5d182 { $return = 'OpsDashSharePointApp' } + 48229a4a-9f1d-413a-8b96-4c02462c0360 { $return = 'OpsDashSharePointApp' } + 48717084-a59c-4306-9dc4-3f618dbecdf9 { $return = '"Napa" Office 365 Development Tools' } + c859ff33-eb41-4ba6-8093-a2c5153bbd7c { $return = 'Workflow' } + 67cad61c-3411-48d7-ab73-561c64f11ed6 { $return = 'Workflow' } + 914ed757-9257-4200-b68e-a2bed2f12c5a { $return = 'RbacBackfill' } + 499b84ac-1321-427f-aa17-267ca6975798 { $return = 'Microsoft.VisualStudio.Online' } + b2590339-0887-4e94-93aa-13357eb510d7 { $return = 'Workflow' } + 0000001b-0000-0000-c000-000000000000 { $return = 'Microsoft Power BI Information Service' } + 89f80565-bfac-4c01-9535-9f0eba332ffe { $return = 'Power BI' } + 433895fb-4ec7-45c3-a53c-c44d10f80d5b { $return = 'Compromised Account Service' } + d7c17728-4f1e-4a1e-86cf-7e0adf3fe903 { $return = 'Workflow' } + 17ef6d31-381f-4783-b186-7b440a3c85c1 { $return = 'Workflow' } + 00000012-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.RMS' } + 81ce94d4-9422-4c0d-a4b9-3250659366ce { $return = 'Workflow' } + 8d3a7d3c-c034-4f19-a2ef-8412952a9671 { $return = 'MicrosoftOffice' } + 0469d4cd-df37-4d93-8a61-f8c75b809164 { $return = 'Microsoft Policy Administration Service' } + 31d3f3f5-7267-45a8-9549-affb00110054 { $return = 'Windows Azure RemoteApp Service' } + 4e004241-32db-46c2-a86f-aaaba29bea9c { $return = 'Workflow' } + 748d098e-7a3b-436d-8b0a-006a58b29647 { $return = 'Workflow' } + dbf08535-1d3b-4f89-bf54-1d48dd613a61 { $return = 'Workflow' } + ed9fe1ef-25a4-482f-9981-2b60f91e2448 { $return = 'Workflow' } + 8ad28d50-ee26-42fc-8a29-e41ea38461f2 { $return = 'Office365RESTAPIExplorer.Office365App' } + 38285dce-a13d-4107-9b04-3016b941bb3a { $return = 'BasicDataOperationsREST' } + 92bb96c8-321c-47f9-bcc5-8849490c2b07 { $return = 'BasicSelfHostedAppREST' } + 488a57a0-00e2-4817-8c8d-cf8a15a994d2 { $return = 'WindowsFormsApplication2.Office365App' } + 11c174dc-1945-4a9a-a36b-c79a0f246b9b { $return = 'AzureApplicationInsights' } + e6acb561-0d94-4287-bd3a-3169f421b112 { $return = 'Tutum' } + 7b77b3a2-8490-49e1-8842-207cd0899af9 { $return = 'Nearpod' } + 0000000a-0000-0000-c000-000000000000 { $return = 'Microsoft.Intune' } + 93625bc8-bfe2-437a-97e0-3d0060024faa { $return = 'SelfServicePasswordReset' } + dee7ba80-6a55-4f3b-a86c-746a9231ae49 { $return = 'MicrosoftAppPlatEMA' } + 803ee9ca-3f7f-4824-bd6e-0b99d720c35c { $return = 'Azure Media Service' } + 2d4d3d8e-2be3-4bef-9f87-7875a61c29de { $return = 'OneNote' } + 8d40666e-5abf-45f6-a5e7-b7192d6d56ed { $return = 'Workflow' } + 262044b1-e2ce-469f-a196-69ab7ada62d3 { $return = 'Backup Management Service' } + 087a2c70-c89e-463f-8dd3-e3959eabb1a9 { $return = 'Microsoft Profile Service Platform Service' } + 7cd684f4-8a78-49b0-91ec-6a35d38739ba { $return = 'Azure Logic Apps' } + c5393580-f805-4401-95e8-94b7a6ef2fc2 { $return = 'Office 365 Management APIs' } + 96231a05-34ce-4eb4-aa6a-70759cbb5e83 { $return = 'MicrosoftAzureRedisCache' } + b8340c3b-9267-498f-b21a-15d5547fd85e { $return = 'Hyper-V Recovery Manager' } + abfa0a7c-a6b6-4736-8310-5855508787cd { $return = 'Microsoft.Azure.WebSites' } + c44b4083-3bb0-49c1-b47d-974e53cbdf3c { $return = 'IbizaPortal' } + 905fcf26-4eb7-48a0-9ff0-8dcc7194b5ba { $return = 'Sway' } + b10686fd-6ba8-49f2-a3cd-67e4d2f52ac8 { $return = 'NovoEd' } + c606301c-f764-4e6b-aa45-7caaaea93c9a { $return = 'OfficeStore' } + 569e8598-685b-4ba2-8bff-5bced483ac46 { $return = 'Evercontact' } + 20a23a2f-8c32-4de7-8063-8c8f909602c0 { $return = 'Workflow' } + aaf214cc-8013-4b95-975f-13203ae36039 { $return = 'Power BI Tiles' } + d88a361a-d488-4271-a13f-a83df7dd99c2 { $return = 'IDML Graph Resolver Service and CAD' } + dff9b531-6290-4620-afce-26826a62a4e7 { $return = 'DocuSign' } + 01cb2876-7ebd-4aa4-9cc9-d28bd4d359a9 { $return = 'Device Registration Service' } + 3290e3f7-d3ac-4165-bcef-cf4874fc4270 { $return = 'Smartsheet' } + a4ee6867-8640-4495-b1fd-8b26037a5bd3 { $return = 'Workflow' } + aa0e3dd4-df02-478d-869e-fc61dd71b6e8 { $return = 'Workflow' } + 0f6edad5-48f2-4585-a609-d252b1c52770 { $return = 'AIGraphClient' } + 0c8139b5-d545-4448-8d2b-2121bb242680 { $return = 'BillingExtension' } + 475226c6-020e-4fb2-8a90-7a972cbfc1d4 { $return = 'KratosAppsService' } + 39624784-6cbe-4a60-afbe-9f46d10fdb27 { $return = 'SkypeForBusinessRemotePowershell' } + 8bdebf23-c0fe-4187-a378-717ad86f6a53 { $return = 'ResourceHealthRP' } + c161e42e-d4df-4a3d-9b42-e7a3c31f59d4 { $return = 'MicrosoftIntuneAPI' } + 9cb77803-d937-493e-9a3b-4b49de3f5a74 { $return = 'MicrosoftIntuneServiceDiscovery' } + ddbf3205-c6bd-46ae-8127-60eb93363864 { $return = 'Microsoft Azure Batch' } + 80ccca67-54bd-44ab-8625-4b79c4dc7775 { $return = 'ComplianceCenter' } + 0a5f63c0-b750-4f38-a71c-4fc0d58b89e2 { $return = 'Microsoft Mobile Application Management' } + e1335bb1-2aec-4f92-8140-0e6e61ae77e5 { $return = 'CIWebService' } + 75018fbe-21fe-4a57-b63c-83252b5eaf16 { $return = 'TeamImprover - Team Organization Chart' } + a393296b-5695-4463-97cb-9fa8638a494a { $return = 'My SharePoint Sites' } + fe217466-5583-431c-9531-14ff7268b7b3 { $return = 'Microsoft Education' } + 5bfe8a29-054e-4348-9e7a-3981b26b125f { $return = 'Bing Places for Business' } + eaf8a961-f56e-47eb-9ffd-936e22a554ef { $return = 'DevilFish' } + 4b4b1d56-1f03-47d9-a0a3-87d4afc913c9 { $return = 'Wunderlist' } + 00000003-0000-0000-c000-000000000000 { $return = 'Microsoft Graph' } + 60e6cd67-9c8c-4951-9b3c-23c25a2169af { $return = 'Compute Resource Provider' } + 507bc9da-c4e2-40cb-96a7-ac90df92685c { $return = 'Office365Reports' } + 09abbdfd-ed23-44ee-a2d9-a627aa1c90f3 { $return = 'ProjectWorkManagement' } + 28ec9756-deaf-48b2-84d5-a623b99af263 { $return = 'Office Personal Assistant at Work Service' } + 9e4a5442-a5c9-4f6f-b03f-5b9fcaaf24b1 { $return = 'OfficeServicesManager' } + 3138fe80-4087-4b04-80a6-8866c738028a { $return = 'SharePoint Notification Service' } + d2a0a418-0aac-4541-82b2-b3142c89da77 { $return = 'MicrosoftAzureOperationalInsights' } + 2cf9eb86-36b5-49dc-86ae-9a63135dfa8c { $return = 'AzureTrafficManagerandDNS' } + 32613fc5-e7ac-4894-ac94-fbc39c9f3e4a { $return = 'OAuth Sandbox' } + 925eb0d0-da50-4604-a19f-bd8de9147958 { $return = 'Groupies Web Service' } + e4ab13ed-33cb-41b4-9140-6e264582cf85 { $return = 'Azure SQL Database Backup To Azure Backup Vault' } + ad230543-afbe-4bb4-ac4f-d94d101704f8 { $return = 'Apiary for Power BI' } + 11cd3e2e-fccb-42ad-ad00-878b93575e07 { $return = 'Automated Call Distribution' } + de17788e-c765-4d31-aba4-fb837cfff174 { $return = 'Skype for Business Management Reporting and Analytics' } + 65d91a3d-ab74-42e6-8a2f-0add61688c74 { $return = 'Microsoft Approval Management' } + 5225545c-3ebd-400f-b668-c8d78550d776 { $return = 'Office Agent Service' } + 1cda9b54-9852-4a5a-96d4-c2ab174f9edf { $return = 'O365Account' } + 4747d38e-36c5-4bc3-979b-b0ef74df54d1 { $return = 'PushChannel' } + b97b6bd4-a49f-4a0c-af18-af507d1da76c { $return = 'Office Shredding Service' } + d4ebce55-015a-49b5-a083-c84d1797ae8c { $return = 'Microsoft Intune Enrollment' } + 5b20c633-9a48-4a5f-95f6-dae91879051f { $return = 'Azure Information Protection' } + 441509e5-a165-4363-8ee7-bcf0b7d26739 { $return = 'EnterpriseAgentPlatform' } + e691bce4-6612-4025-b94c-81372a99f77e { $return = 'Boomerang' } + 8edd93e1-2103-40b4-bd70-6e34e586362d { $return = 'Windows Azure Security Resource Provider' } + 94c63fef-13a3-47bc-8074-75af8c65887a { $return = 'Office Delve' } + e95d8bee-4725-4f59-910d-94d415da51b9 { $return = 'Skype for Business Name Dictionary Service' } + e3c5dbcd-bb5f-4bda-b943-adc7a5bbc65e { $return = 'Workflow' } + 8602e328-9b72-4f2d-a4ae-1387d013a2b3 { $return = 'Azure API Management' } + 8b3391f4-af01-4ee8-b4ea-9871b2499735 { $return = 'O365 Secure Score' } + c26550d6-bc82-4484-82ca-ac1c75308ca3 { $return = 'Office 365 YammerOnOls' } + 33be1cef-03fb-444b-8fd3-08ca1b4d803f { $return = 'OneDrive Web' } + dcad865d-9257-4521-ad4d-bae3e137b345 { $return = 'Microsoft SharePoint Online - SharePoint Home' } + b2cc270f-563e-4d8a-af47-f00963a71dcd { $return = 'OneProfile Service' } + 4660504c-45b3-4674-a709-71951a6b0763 { $return = 'Microsoft Invitation Acceptance Portal' } + ba23cd2a-306c-48f2-9d62-d3ecd372dfe4 { $return = 'OfficeGraph' } + d52485ee-4609-4f6b-b3a3-68b6f841fa23 { $return = 'On-Premises Data Gateway Connector' } + 996def3d-b36c-4153-8607-a6fd3c01b89f { $return = 'Dynamics 365 for Financials' } + b6b84568-6c01-4981-a80f-09da9a20bbed { $return = 'Microsoft Invoicing' } + 9d3e55ba-79e0-4b7c-af50-dc460b81dca1 { $return = 'Microsoft Azure Data Catalog' } + 4345a7b9-9a63-4910-a426-35363201d503 { $return = 'O365 Suite UX' } + ac815d4a-573b-4174-b38e-46490d19f894 { $return = 'Workflow' } + bb8f18b0-9c38-48c9-a847-e1ef3af0602d { $return = 'Microsoft.Azure.ActiveDirectoryIUX' } + cc15fd57-2c6c-4117-a88c-83b1d56b4bbe { $return = 'Microsoft Teams Services' } + 5e3ce6c0-2b1f-4285-8d4b-75ee78787346 { $return = 'Skype Teams' } + 1fec8e78-bce4-4aaf-ab1b-5451cc387264 { $return = 'Microsoft Teams' } + 6d32b7f8-782e-43e0-ac47-aaad9f4eb839 { $return = 'Permission Service O365' } + cdccd920-384b-4a25-897d-75161a4b74c1 { $return = 'Skype Teams Firehose' } + 1c0ae35a-e2ec-4592-8e08-c40884656fa5 { $return = 'Skype Team Substrate connector' } + cf6c77f8-914f-4078-baef-e39a5181158b { $return = 'Skype Teams Settings Store' } + 64f79cb9-9c82-4199-b85b-77e35b7dcbcb { $return = 'Microsoft Teams Bots' } + b7912db9-aa33-4820-9d4f-709830fdd78f { $return = 'ConnectionsService' } + 82f77645-8a66-4745-bcdf-9706824f9ad0 { $return = 'PowerApps Runtime Service' } + 6204c1d1-4712-4c46-a7d9-3ed63d992682 { $return = 'Microsoft Flow Portal' } + 7df0a125-d3be-4c96-aa54-591f83ff541c { $return = 'Microsoft Flow Service' } + 331cc017-5973-4173-b270-f0042fddfd75 { $return = 'PowerAppsService' } + 0a0e9e37-25e3-47d4-964c-5b8237cad19a { $return = 'CloudSponge' } + df09ff61-2178-45d8-888c-4210c1c7b0b2 { $return = 'O365 UAP Processor' } + 8338dec2-e1b3-48f7-8438-20c30a534458 { $return = 'ViewPoint' } + 00000001-0000-0000-c000-000000000000 { $return = 'Azure ESTS Service' } + 394866fc-eedb-4f01-8536-3ff84b16be2a { $return = 'Microsoft People Cards Service' } + 0a0a29f9-0a25-49c7-94bf-c53c3f8fa69d { $return = 'Cortana Experience with O365' } + bb2a2e3a-c5e7-4f0a-88e0-8e01fd3fc1f4 { $return = 'CPIM Service' } + 0004c632-673b-4105-9bb6-f3bbd2a927fe { $return = 'PowerApps and Flow' } + d3ce4cf8-6810-442d-b42e-375e14710095 { $return = 'Graph Explorer' } + 3aa5c166-136f-40eb-9066-33ac63099211 { $return = 'O365 Customer Monitoring' } + d6fdaa33-e821-4211-83d0-cf74736489e1 { $return = 'Microsoft Service Trust' } + ef4a2a24-4b4e-4abf-93ba-cc11c5bd442c { $return = 'Edmodo' } + b692184e-b47f-4706-b352-84b288d2d9ee { $return = 'Microsoft.MileIQ.RESTService' } + a25dbca8-4e60-48e5-80a2-0664fdb5c9b6 { $return = 'Microsoft.MileIQ' } + f7069a8d-9edc-4300-b365-ae53c9627fc4 { $return = 'Microsoft.MileIQ.Dashboard' } + 02e3ae74-c151-4bda-b8f0-55fbf341de08 { $return = 'Application Registration Portal' } + 1f5530b3-261a-47a9-b357-ded261e17918 { $return = 'Azure Multi-Factor Auth Connector' } + 981f26a1-7f43-403b-a875-f8b09b8cd720 { $return = 'Azure Multi-Factor Auth Client' } + 6ea8091b-151d-447a-9013-6845b83ba57b { $return = 'AD Hybrid Health' } + fc68d9e5-1f76-45ef-99aa-214805418498 { $return = 'Azure AD Identity Protection' } + 01fc33a7-78ba-4d2f-a4b7-768e336e890e { $return = 'MS-PIM' } + a6aa9161-5291-40bb-8c5c-923b567bee3b { $return = 'Storage Resource Provider' } + 4e9b8b9a-1001-4017-8dd1-6e8f25e19d13 { $return = 'Adobe Acrobat' } + 159b90bb-bb28-4568-ad7c-adad6b814a2f { $return = 'LastPass' } + b4bddae8-ab25-483e-8670-df09b9f1d0ea { $return = 'Signup' } + aa580612-c342-4ace-9055-8edee43ccb89 { $return = 'Microsoft StaffHub' } + 51133ff5-8e0d-4078-bcca-84fb7f905b64 { $return = 'Microsoft Teams Mailhook' } + ab3be6b7-f5df-413d-ac2d-abf1e3fd9c0b { $return = 'Microsoft Teams Graph Service' } + b1379a75-ce5e-4fa3-80c6-89bb39bf646c { $return = 'Microsoft Teams Chat Aggregator' } + 48af08dc-f6d2-435f-b2a7-069abd99c086 { $return = 'Connectors' } + d676e816-a17b-416b-ac1a-05ad96f43686 { $return = 'Workflow' } + cfa8b339-82a2-471a-a3c9-0fc0be7a4093 { $return = 'Azure Key Vault' } + c2f89f53-3971-4e09-8656-18eed74aee10 { $return = 'calendly' } + 6da466b6-1d13-4a2c-97bd-51a99e8d4d74 { $return = 'Exchange Office Graph Client for AAD - Interactive' } + 0eda3b13-ddc9-4c25-b7dd-2f6ea073d6b7 { $return = 'Microsoft Flow CDS Integration Service' } + eacba838-453c-4d3e-8c6a-eb815d3469a3 { $return = 'Microsoft Flow CDS Integration Service TIP1' } + 4ac7d521-0382-477b-b0f8-7e1d95f85ca2 { $return = 'SQL Server Analysis Services Azure' } + b4114287-89e4-4209-bd99-b7d4919bcf64 { $return = 'OfficeDelve' } + 4580fd1d-e5a3-4f56-9ad1-aab0e3bf8f76 { $return = 'Call Recorder' } + a855a166-fd92-4c76-b60d-a791e0762432 { $return = 'Microsoft Teams VSTS' } + c37c294f-eec8-47d2-b3e2-fc3daa8f77d3 { $return = 'Workflow' } + fc75330b-179d-49af-87dd-3b1acf6827fa { $return = 'AzureAutomationAADPatchS2S' } + 766d89a4-d6a6-444d-8a5e-e1a18622288a { $return = 'OneDrive' } + f16c4a38-5aff-4549-8199-ee7d3c5bd8dc { $return = 'Workflow' } + 4c4f550b-42b2-4a16-93f9-fdb9e01bb6ed { $return = 'Targeted Messaging Service' } + 765fe668-04e7-42ba-aec0-2c96f1d8b652 { $return = 'Exchange Office Graph Client for AAD - Noninteractive' } + 0130cc9f-7ac5-4026-bd5f-80a08a54e6d9 { $return = 'Azure Data Warehouse Polybase' } + a1cf9e0a-fe14-487c-beb9-dd3360921173 { $return = 'Meetup' } + 76cd24bf-a9fc-4344-b1dc-908275de6d6d { $return = 'Azure SQL Virtual Network to Network Resource Provider' } + 9f505dbd-a32c-4685-b1c6-72e4ef704cb0 { $return = 'Amazon Alexa' } + 1e2ca66a-c176-45ea-a877-e87f7231e0ee { $return = 'Microsoft B2B Admin Worker' } + 2634dd23-5e5a-431c-81ca-11710d9079f4 { $return = 'Microsoft Stream Service' } + cf53fce8-def6-4aeb-8d30-b158e7b1cf83 { $return = 'Microsoft Stream Portal' } + c9a559d2-7aab-4f13-a6ed-e7e9c52aec87 { $return = 'Microsoft Forms' } + 978877ea-b2d6-458b-80c7-05df932f3723 { $return = 'Microsoft Teams AuditService' } + dbc36ae1-c097-4df9-8d94-343c3d091a76 { $return = 'Service Encryption' } + fa7ff576-8e31-4a58-a5e5-780c1cd57caa { $return = 'OneNote' } + cb4dc29f-0bf4-402a-8b30-7511498ed654 { $return = 'Power BI Premium' } + f5aeb603-2a64-4f37-b9a8-b544f3542865 { $return = 'Microsoft Teams RetentionHook Service' } + da109bdd-abda-4c06-8808-4655199420f8 { $return = 'Glip Contacts' } + 76c7f279-7959-468f-8943-3954880e0d8c { $return = 'Azure SQL Managed Instance to Microsoft.Network' } + 3a9ddf38-83f3-4ea1-a33a-ecf934644e2d { $return = 'Protected Message Viewer' } + 5635d99c-c364-4411-90eb-764a511b5fdf { $return = 'Responsive Banner Slider' } + a43e5392-f48b-46a4-a0f1-098b5eeb4757 { $return = 'Cloudsponge' } + d73f4b35-55c9-48c7-8b10-651f6f2acb2e { $return = 'MCAPI Authorization Prod' } + 166f1b03-5b19-416f-a94b-1d7aa2d247dc { $return = 'Office Hive' } + b815ce1c-748f-4b1e-9270-a42c1fa4485a { $return = 'Workflow' } + bd7b778b-4aa8-4cde-8d90-8aeb821c0bd2 { $return = 'Workflow' } + 9d06afd9-66c9-49a6-b385-ea7509332b0b { $return = 'O365SBRM Service' } + 9ea1ad79-fdb6-4f9a-8bc3-2b70f96e34c7 { $return = 'Bing' } + 57fb890c-0dab-4253-a5e0-7188c88b2bb4 { $return = 'SharePoint Online Client' } + 45c10911-200f-4e27-a666-9e9fca147395 { $return = 'drawio' } + b73f62d0-210b-4396-a4c5-ea50c4fab79b { $return = 'Skype Business Voice Fraud Detection and Prevention' } + bc59ab01-8403-45c6-8796-ac3ef710b3e3 { $return = 'Outlook Online Add-in App' } + 035f9e1d-4f00-4419-bf50-bf2d87eb4878 { $return = 'Azure Monitor Restricted' } + 7c33bfcb-8d33-48d6-8e60-dc6404003489 { $return = 'Network Watcher' } + a0be0c72-870e-46f0-9c49-c98333a996f7 { $return = 'AzureDnsFrontendApp' } + 1e3e4475-288f-4018-a376-df66fd7fac5f { $return = 'NetworkTrafficAnalyticsService' } + 7557eb47-c689-4224-abcf-aef9bd7573df { $return = 'Skype for Business' } + c39c9bac-9d1f-4dfb-aa29-27f6365e5cb7 { $return = 'Azure Advisor' } + 2087bd82-7206-4c0a-b305-1321a39e5926 { $return = 'Microsoft To-Do' } + f8d98a96-0999-43f5-8af3-69971c7bb423 { $return = 'iOS Accounts' } + c27373d3-335f-4b45-8af9-fe81c240d377 { $return = 'P2P Server' } + 5c2ffddc-f1d7-4dc3-926e-3c1bd98e32bd { $return = 'RITS Dev' } + 982bda36-4632-4165-a46a-9863b1bbcf7d { $return = 'O365 Demeter' } + 98c8388a-4e86-424f-a176-d1288462816f { $return = 'OfficeFeedProcessors' } + bf9fc203-c1ff-4fd4-878b-323642e462ec { $return = 'Jarvis Transaction Service' } + 257601fd-462f-4a21-b623-7f719f0f90f4 { $return = 'Centralized Deployment' } + 2a486b53-dbd2-49c0-a2bc-278bdfc30833 { $return = 'Cortana at Work Service' } + 22d7579f-06c2-4baa-89d2-e844486adb9d { $return = 'Cortana at Work Bing Services' } + 4c8f074c-e32b-4ba7-b072-0f39d71daf51 { $return = 'IPSubstrate' } + a164aee5-7d0a-46bb-9404-37421d58bdf7 { $return = 'Microsoft Teams AuthSvc' } + 354b5b6d-abd6-4736-9f51-1be80049b91f { $return = 'Microsoft Mobile Application Management Backend' } + 82b293b2-d54d-4d59-9a95-39c1c97954a7 { $return = 'Tasks in a Box' } + fdc83783-b652-4258-a622-66bc85f1a871 { $return = 'FedExPackageTracking' } + d0597157-f0ae-4e23-b06c-9e65de434c4f { $return = 'Microsoft Teams Task Service' } + f5c26e74-f226-4ae8-85f0-b4af0080ac9e { $return = 'Application Insights API' } + 57c0fc58-a83a-41d0-8ae9-08952659bdfd { $return = 'Azure Cosmos DB Virtual Network To Network Resource Provider' } + 744e50be-c4ff-4e90-8061-cd7f1fabac0b { $return = 'LinkedIn Microsoft Graph Connector' } + 823dfde0-1b9a-415a-a35a-1ad34e16dd44 { $return = 'Microsoft Teams Wiki Images Migration' } + 3ab9b3bc-762f-4d62-82f7-7e1d653ce29f { $return = 'Microsoft Volume Licensing' } + 44eb7794-0e11-42b6-800b-dc31874f9f60 { $return = 'Alignable' } + c58637bb-e2e1-4312-8a00-04b5ffcd3403 { $return = 'SharePoint Online Client Extensibility' } + 62b732f7-fc71-40bc-b27d-35efcb0509de { $return = 'Microsoft Teams AadSync' } + 07978fee-621a-42df-82bb-3eabc6511c26 { $return = 'SurveyMonkey' } + 47ee738b-3f1a-4fc7-ab11-37e4822b007e { $return = 'Azure AD Application Proxy' } + 00000007-0000-0000-c000-000000000000 { $return = 'Dynamics CRM Online' } + 913c6de4-2a4a-4a61-a9ce-945d2b2ce2e0 { $return = 'Dynamics Lifecycle services' } + f217ad13-46b8-4c5b-b661-876ccdf37302 { $return = 'Attach OneDrive files to Asana' } + 00000008-0000-0000-c000-000000000000 { $return = 'Microsoft.Azure.DataMarket' } + 9b06ebd4-9068-486b-bdd2-dac26b8a5a7a { $return = 'Microsoft.DynamicsMarketing' } + e8ab36af-d4be-4833-a38b-4d6cf1cfd525 { $return = 'Microsoft Social Engagement' } + 8909aac3-be91-470c-8a0b-ff09d669af91 { $return = 'Microsoft Parature Dynamics CRM' } + 71234da4-b92f-429d-b8ec-6e62652e50d7 { $return = 'Microsoft Customer Engagement Portal' } + b861dbcc-a7ef-4219-a005-0e4de4ea7dcf { $return = 'Data Export Service for Microsoft Dynamics 365' } + 2db8cb1d-fb6c-450b-ab09-49b6ae35186b { $return = 'Microsoft Dynamics CRM Learning Path' } + 2e49aa60-1bd3-43b6-8ab6-03ada3d9f08b { $return = 'Dynamics Data Integration' } + } + + if ([string]::IsNullOrEmpty($return)) { + $return = $Applications | ? { $_.Appid -eq $ID } | Select -ExpandProperty DisplayName + } + + if ([string]::IsNullOrEmpty($return)) { + $return = $Applications | ? { $_.ID -eq $ID } | Select -ExpandProperty DisplayName + } + + if ([string]::IsNullOrEmpty($return)) { + $return = '' + } + + return $return +} + +# Write to the Azure Functions log stream. +Write-Host "PowerShell HTTP trigger function processed a request." + +# Interact with query parameters or the body of the request. +$TenantFilter = $Request.Query.TenantFilter +$UserID = $Request.Query.UserID + + +$json = '{"conditions":{"users":{"allUsers":2,"included":{"userIds":["' + $UserID + '"],"groupIds":[]},"excluded":{"userIds":[],"groupIds":[]}},"servicePrincipals":{"allServicePrincipals":1,"includeAllMicrosoftApps":false,"excludeAllMicrosoftApps":false,"userActions":[],"stepUpTags":[]},"conditions":{"minUserRisk":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"minSigninRisk":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"servicePrincipalRiskLevels":{"noRisk":false,"lowRisk":false,"mediumRisk":false,"highRisk":false,"applyCondition":false},"devicePlatforms":{"all":2,"included":{"android":false,"ios":false,"windowsPhone":false,"windows":false,"macOs":false,"linux":false},"excluded":null,"applyCondition":false},"locations":{"applyCondition":true,"includeLocationType":2,"excludeAllTrusted":false},"clientApps":{"applyCondition":false,"specificClientApps":false,"webBrowsers":false,"exchangeActiveSync":false,"onlyAllowSupportedPlatforms":false,"mobileDesktop":false},"clientAppsV2":{"applyCondition":false,"webBrowsers":false,"mobileDesktop":false,"modernAuth":false,"exchangeActiveSync":false,"onlyAllowSupportedPlatforms":false,"otherClients":false},"deviceState":{"includeDeviceStateType":1,"excludeDomainJoionedDevice":false,"excludeCompliantDevice":false,"applyCondition":true}}},"country":"","device":{}}' +$UserPolicies = (New-ClassicAPIPostRequest -uri "https://main.iam.ad.ext.azure.com/api/Policies/Evaluate?" -tenantid $tenantfilter -Method POST -body $json -resource '74658136-14ec-4630-ad9b-26e160ff0fc6' -verbose | where-object { $_.applied -eq $true }) + + + + +$ConditionalAccessPolicyOutput = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/policies" -tenantid $tenantfilter +$AllNamedLocations = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/identity/conditionalAccess/namedLocations" -tenantid $tenantfilter +$AllApplications = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/applications" -tenantid $tenantfilter +$AllRoleDefinitions = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/roleManagement/directory/roleDefinitions" -tenantid $tenantfilter +$GroupListOutput = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/groups" -tenantid $tenantfilter +$UserListOutput = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users" -tenantid $tenantfilter | Select-Object * -ExcludeProperty *extensionAttribute* + +$GraphRequest = foreach ($cap in $ConditionalAccessPolicyOutput) { + if ($cap.id -in $UserPolicies.policyId) { + $temp = [PSCustomObject]@{ + id = $cap.id + displayName = $cap.displayName + customer = $cap.Customer + tenantID = $cap.TenantID + createdDateTime = $(if (![string]::IsNullOrEmpty($cap.createdDateTime)) { [datetime]$cap.createdDateTime | Get-Date -Format "yyyy-MM-dd HH:mm" }else { "" }) + modifiedDateTime = $(if (![string]::IsNullOrEmpty($cap.modifiedDateTime)) { [datetime]$cap.modifiedDateTime | Get-Date -Format "yyyy-MM-dd HH:mm" }else { "" }) + state = $cap.state + clientAppTypes = ($cap.conditions.clientAppTypes) -join "," + includePlatforms = ($cap.conditions.platforms.includePlatforms) -join "," + excludePlatforms = ($cap.conditions.platforms.excludePlatforms) -join "," + includeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.includeLocations) -join "," + excludeLocations = (Get-LocationNameFromId -Locations $AllNamedLocations -id $cap.conditions.locations.excludeLocations) -join "," + includeApplications = ($cap.conditions.applications.includeApplications | % { Get-ApplicationNameFromId -Applications $AllApplications -id $_ }) -join "," + excludeApplications = ($cap.conditions.applications.excludeApplications | % { Get-ApplicationNameFromId -Applications $AllApplications -id $_ }) -join "," + includeUserActions = ($cap.conditions.applications.includeUserActions | out-string) + includeAuthenticationContextClassReferences = ($cap.conditions.applications.includeAuthenticationContextClassReferences | out-string) + includeUsers = ($cap.conditions.users.includeUsers | % { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String + excludeUsers = ($cap.conditions.users.excludeUsers | % { Get-UserNameFromId -Users $UserListOutput -id $_ }) | Out-String + includeGroups = ($cap.conditions.users.includeGroups | % { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String + excludeGroups = ($cap.conditions.users.excludeGroups | % { Get-GroupNameFromId -Groups $GroupListOutput -id $_ }) | Out-String + includeRoles = ($cap.conditions.users.includeRoles | % { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String + excludeRoles = ($cap.conditions.users.excludeRoles | % { Get-RoleNameFromId -RoleDefinitions $AllRoleDefinitions -id $_ }) | Out-String + grantControlsOperator = ($cap.grantControls.operator) -join "," + builtInControls = ($cap.grantControls.builtInControls) -join "," + customAuthenticationFactors = ($cap.grantControls.customAuthenticationFactors) -join "," + termsOfUse = ($cap.grantControls.termsOfUse) -join "," + } + $temp + } +} + +Write-Host $GraphRequest + +# Associate values to output bindings by calling 'Push-OutputBinding'. +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($GraphRequest) + }) diff --git a/ListUserDevices/function.json b/ListUserDevices/function.json new file mode 100644 index 000000000000..306b0c51e560 --- /dev/null +++ b/ListUserDevices/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} \ No newline at end of file diff --git a/ListUserDevices/run.ps1 b/ListUserDevices/run.ps1 new file mode 100644 index 000000000000..463457df9ae0 --- /dev/null +++ b/ListUserDevices/run.ps1 @@ -0,0 +1,36 @@ +using namespace System.Net + +# Input bindings are passed in via param block. +param($Request, $TriggerMetadata) + +$APIName = $TriggerMetadata.FunctionName +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" + + +# Write to the Azure Functions log stream. +Write-Host "PowerShell HTTP trigger function processed a request." + +# Interact with query parameters or the body of the request. +$TenantFilter = $Request.Query.TenantFilter +$UserID = $Request.Query.UserID +$GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$UserID/ownedDevices?`$top=999" -Tenantid $tenantfilter | Select-Object @{ Name = 'ID'; Expression = { $_.'id' } }, +@{ Name = 'accountEnabled'; Expression = { $_.'accountEnabled' } }, +@{ Name = 'approximateLastSignInDateTime'; Expression = { $_.'approximateLastSignInDateTime' | Out-String } }, +@{ Name = 'createdDateTime'; Expression = { $_.'createdDateTime' | Out-String } }, +@{ Name = 'deviceOwnership'; Expression = { $_.'deviceOwnership' } }, +@{ Name = 'displayName'; Expression = { $_.'displayName' } }, +@{ Name = 'enrollmentType'; Expression = { $_.'enrollmentType' } }, +@{ Name = 'isCompliant'; Expression = { $_.'isCompliant' } }, +@{ Name = 'managementType'; Expression = { $_.'managementType' } }, +@{ Name = 'manufacturer'; Expression = { $_.'manufacturer' } }, +@{ Name = 'model'; Expression = { $_.'model' } }, +@{ Name = 'operatingSystem'; Expression = { $_.'operatingSystem' } }, +@{ Name = 'onPremisesSyncEnabled'; Expression = { $(if([string]::IsNullOrEmpty($_.'onPremisesSyncEnabled')){$false}else{$true}) } }, +@{ Name = 'operatingSystemVersion'; Expression = { $_.'operatingSystemVersion' } }, +@{ Name = 'trustType'; Expression = { $_.'trustType' } } + +# Associate values to output bindings by calling 'Push-OutputBinding'. +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($GraphRequest) + }) diff --git a/ListUserSigninLogs/function.json b/ListUserSigninLogs/function.json new file mode 100644 index 000000000000..306b0c51e560 --- /dev/null +++ b/ListUserSigninLogs/function.json @@ -0,0 +1,19 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "type": "httpTrigger", + "direction": "in", + "name": "Request", + "methods": [ + "get", + "post" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + } + ] +} \ No newline at end of file diff --git a/ListUserSigninLogs/run.ps1 b/ListUserSigninLogs/run.ps1 new file mode 100644 index 000000000000..44844865e34c --- /dev/null +++ b/ListUserSigninLogs/run.ps1 @@ -0,0 +1,38 @@ +using namespace System.Net + +# Input bindings are passed in via param block. +param($Request, $TriggerMetadata) + +$APIName = $TriggerMetadata.FunctionName +Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" + + +# Write to the Azure Functions log stream. +Write-Host "PowerShell HTTP trigger function processed a request." + +# Interact with query parameters or the body of the request. +$TenantFilter = $Request.Query.TenantFilter +$UserID = $Request.Query.UserID + +$StartTime = Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z' +$EndDate = (Get-Date).addDays(-1) +$EndTime = Get-Date (Get-Date($EndDate)).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z' + + +$URI = "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=(userId eq '$UserID')&`$top=50&`$orderby=createdDateTime desc" +Write-Host $URI +$GraphRequest = New-GraphGetRequest -uri $URI -tenantid $TenantFilter -verbose + +#$GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/reports/get$($type)Detail(period='D7')" -tenantid $TenantFilter | convertfrom-csv | select-object @{ Name = 'UPN'; Expression = { $_.'Owner Principal Name' } }, +#@{ Name = 'displayName'; Expression = { $_.'Owner Display Name' } }, +#@{ Name = 'LastActive'; Expression = { $_.'Last Activity Date' } }, +#@{ Name = 'FileCount'; Expression = { $_.'File Count' } }, +#@{ Name = 'UsedGB'; Expression = { [math]::round($_.'Storage Used (Byte)' /1GB,0) } }, +#@{ Name = 'URL'; Expression = { $_.'Site URL' } }, +#@{ Name = 'Allocated'; Expression = { $_.'Storage Allocated (Byte)' /1GB } } + +# Associate values to output bindings by calling 'Push-OutputBinding'. +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = @($GraphRequest) + }) \ No newline at end of file diff --git a/ListUsers/run.ps1 b/ListUsers/run.ps1 index b9047bb574c2..ea61e458ace4 100644 --- a/ListUsers/run.ps1 +++ b/ListUsers/run.ps1 @@ -6,7 +6,7 @@ param($Request, $TriggerMetadata) $APIName = $TriggerMetadata.FunctionName Log-Request -user $request.headers.'x-ms-client-principal' -API $APINAME -message "Accessed this API" -Sev "Debug" -$selectlist = "id", "accountEnabled", "businessPhones", "city", "createdDateTime", "companyName", "country", "department", "displayName", "faxNumber", "givenName", "isResourceAccount", "jobTitle", "mail", "mailNickname", "mobilePhone", "onPremisesDistinguishedName", "officeLocation", "onPremisesLastSyncDateTime", "otherMails", "postalCode", "preferredDataLocation", "preferredLanguage", "proxyAddresses", "showInAddressList", "state", "streetAddress", "surname", "usageLocation", "userPrincipalName", "userType", "assignedLicenses", "onPremisesSyncEnabled", "LicJoined", "Aliasses" +$selectlist = "id", "accountEnabled", "businessPhones", "city", "createdDateTime", "companyName", "country", "department", "displayName", "faxNumber", "givenName", "isResourceAccount", "jobTitle", "mail", "mailNickname", "mobilePhone", "onPremisesDistinguishedName", "officeLocation", "onPremisesLastSyncDateTime", "otherMails", "postalCode", "preferredDataLocation", "preferredLanguage", "proxyAddresses", "showInAddressList", "state", "streetAddress", "surname", "usageLocation", "userPrincipalName", "userType", "assignedLicenses", "onPremisesSyncEnabled", "LicJoined", "Aliases", "primDomain" # Write to the Azure Functions log stream. Write-Host "PowerShell HTTP trigger function processed a request." @@ -16,17 +16,34 @@ $TenantFilter = $Request.Query.TenantFilter $userid = $Request.Query.UserID $GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users/$($userid)?`$top=999&`$select=$($selectlist -join ',')" -tenantid $TenantFilter | Select-Object $selectlist | ForEach-Object { $_.onPremisesSyncEnabled = [bool]($_.onPremisesSyncEnabled) - $_.Aliasses = $_.Proxyaddresses -join ", " + $_.Aliases = $_.Proxyaddresses -join ", " $SkuID = $_.AssignedLicenses.skuid $_.LicJoined = ($ConvertTable | Where-Object { $_.guid -in $skuid }).'Product_Display_Name' -join ", " + $_.primDomain = ($_.userPrincipalName -split '@' | Select-Object -Last 1) $_ } +if ($userid) { + $uri = "https://login.microsoftonline.com/$($TenantFilter)/oauth2/token" + $body = "resource=https://admin.microsoft.com&grant_type=refresh_token&refresh_token=$($ENV:ExchangeRefreshToken)" + $token = Invoke-RestMethod $uri -Body $body -ContentType "application/x-www-form-urlencoded" -ErrorAction SilentlyContinue -Method post + $LastSignIn = Invoke-RestMethod -ContentType "application/json;charset=UTF-8" -Uri "https://admin.microsoft.com/admin/api/users/$($userid)/lastSignInInfo" -Method GET -Headers @{ + Authorization = "Bearer $($token.access_token)"; + "x-ms-client-request-id" = [guid]::NewGuid().ToString(); + "x-ms-client-session-id" = [guid]::NewGuid().ToString() + 'x-ms-correlation-id' = [guid]::NewGuid() + 'X-Requested-With' = 'XMLHttpRequest' + } + $GraphRequest = $GraphRequest | Select-Object *, + @{ Name = 'LastSigninApplication'; Expression = { $LastSignIn.AppDisplayName } }, + @{ Name = 'LastSigninDate'; Expression = { $($LastSignIn.CreatedDateTime | Out-String) } }, + @{ Name = 'LastSigninStatus'; Expression = { $LastSignIn.Status.AdditionalDetails } }, + @{ Name = 'LastSigninResult'; Expression = { if ($LastSignIn.Status.ErrorCode -eq 0) { "Success" } else { "Failure" } } }, + @{ Name = 'LastSigninFailureReason'; Expression = { if ($LastSignIn.Status.ErrorCode -eq 0) { "Sucessfully signed in" } else { $LastSignIn.status.FailureReason } } } +} # Associate values to output bindings by calling 'Push-OutputBinding'. Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ StatusCode = [HttpStatusCode]::OK Body = @($GraphRequest) - }) - -#@{ Name = 'LicJoined'; Expression = { ($_.assignedLicenses | ForEach-Object { convert-skuname -skuID $_.skuid }) -join ", " } }, @{ Name = 'Aliasses'; Expression = { $_.Proxyaddresses -join ", " } }, @{ Name = 'primDomain'; Expression = { $_.userPrincipalName -split "@" | Select-Object -Last 1 } } \ No newline at end of file + }) \ No newline at end of file diff --git a/RemoveUser/run.ps1 b/RemoveUser/run.ps1 index c6c514f5e9f0..2a0c3f32071a 100644 --- a/RemoveUser/run.ps1 +++ b/RemoveUser/run.ps1 @@ -28,4 +28,4 @@ Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ Body = $body }) -#@{ Name = 'LicJoined'; Expression = { ($_.assignedLicenses | ForEach-Object { convert-skuname -skuID $_.skuid }) -join ", " } }, @{ Name = 'Aliasses'; Expression = { $_.Proxyaddresses -join ", " } }, @{ Name = 'primDomain'; Expression = { $_.userPrincipalName -split "@" | Select-Object -Last 1 } } \ No newline at end of file +#@{ Name = 'LicJoined'; Expression = { ($_.assignedLicenses | ForEach-Object { convert-skuname -skuID $_.skuid }) -join ", " } }, @{ Name = 'Aliases'; Expression = { $_.Proxyaddresses -join ", " } }, @{ Name = 'primDomain'; Expression = { $_.userPrincipalName -split "@" | Select-Object -Last 1 } } \ No newline at end of file diff --git a/SecurityBaselines_All/function.json b/SecurityBaselines_All/function.json new file mode 100644 index 000000000000..2d4ea9094b24 --- /dev/null +++ b/SecurityBaselines_All/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "tenant", + "direction": "in", + "type": "activityTrigger" + } + ] +} \ No newline at end of file diff --git a/SecurityBaselines_All/results.json b/SecurityBaselines_All/results.json new file mode 100644 index 000000000000..2767793f552b --- /dev/null +++ b/SecurityBaselines_All/results.json @@ -0,0 +1 @@ +"WindmillGroup.onmicrosoft.com" diff --git a/SecurityBaselines_All/run.ps1 b/SecurityBaselines_All/run.ps1 new file mode 100644 index 000000000000..a4862f7e8b17 --- /dev/null +++ b/SecurityBaselines_All/run.ps1 @@ -0,0 +1,4 @@ +param($tenant) +#Log-request -API "SecurityBaselines" -tenant $tenant -message "SecurityBaselines_All called at $((Get-Date).tofiletime())" -sev Info +Write-Output $tenant.defaultDomainName + diff --git a/SecurityBaselines_Orchestration/function.json b/SecurityBaselines_Orchestration/function.json new file mode 100644 index 000000000000..7326b39c184d --- /dev/null +++ b/SecurityBaselines_Orchestration/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "Context", + "type": "orchestrationTrigger", + "direction": "in" + } + ] +} \ No newline at end of file diff --git a/SecurityBaselines_Orchestration/run.ps1 b/SecurityBaselines_Orchestration/run.ps1 new file mode 100644 index 000000000000..5f15b2ea36e0 --- /dev/null +++ b/SecurityBaselines_Orchestration/run.ps1 @@ -0,0 +1,24 @@ +param($Context) + +Log-request -API "SecurityBaselines" -tenant $tenant -message "SecurityBaselines_Orchestration called at $((Get-Date).tofiletime())" -sev Info +#Remove-Item "SecurityBaselines_All\results.json" -Force + +New-Item "SecurityBaselines_All\CurrentlyRunning.txt" -ItemType File -Force +$Batch = Get-Tenants +$ParallelTasks = foreach ($Item in $Batch) { + Invoke-DurableActivity -FunctionName "SecurityBaselines_All" -Input $item -NoWait +} + +Log-request -API "SecurityBaselines" -tenant $tenant -message "STARTING PROCESS OF OUTPUTS!" -sev Info +$Outputs = Wait-ActivityFunction -Task $ParallelTasks +Log-request -API "SecurityBaselines" -tenant $tenant -message "Outputs found count = $($Outputs.count)" -sev Info + +foreach ($item in $Outputs) { + Write-Host $Item | Out-String + $Object = $Item | ConvertTo-Json + + Set-Content "SecurityBaselines_All\results.json" -Value $Object -Force +} + +#Log-request -API "DomainAnalyser" -tenant $tenant -message "Domain Analyser has Finished" -sev Info +Remove-Item "SecurityBaselines_All\CurrentlyRunning.txt" -Force \ No newline at end of file diff --git a/SecurityBaselines_OrchestrationStarter/function.json b/SecurityBaselines_OrchestrationStarter/function.json new file mode 100644 index 000000000000..14c44f4f0217 --- /dev/null +++ b/SecurityBaselines_OrchestrationStarter/function.json @@ -0,0 +1,24 @@ +{ + "bindings": [ + { + "authLevel": "anonymous", + "name": "Request", + "type": "httpTrigger", + "direction": "in", + "methods": [ + "post", + "get" + ] + }, + { + "type": "http", + "direction": "out", + "name": "Response" + }, + { + "name": "starter", + "type": "durableClient", + "direction": "in" + } + ] +} diff --git a/SecurityBaselines_OrchestrationStarter/run.ps1 b/SecurityBaselines_OrchestrationStarter/run.ps1 new file mode 100644 index 000000000000..b94c58e14fd2 --- /dev/null +++ b/SecurityBaselines_OrchestrationStarter/run.ps1 @@ -0,0 +1,33 @@ +using namespace System.Net + +param($Request, $TriggerMetadata) +Log-request -API "SecurityBaselines" -tenant $tenant -message "SecurityBaselines_OrchestrationStarter called at $(Get-Date)" -sev Info +$APIName = $TriggerMetadata.FunctionName +$OrchestratorName = "SecurityBaselines_Orchestration" + +$CurrentlyRunning = Get-Item "SecurityBaselines_All\CurrentlyRunning.txt" -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).AddHours(-24) +if ($CurrentlyRunning) { + $Results = [pscustomobject]@{"Results" = "Already running. Please wait for the current instance to finish" } + Log-request -API $APIName -message "Attempted to start an instance but an instance was already running." -sev Info +} +else { + $InstanceId = Start-NewOrchestration -FunctionName $OrchestratorName + Write-Host "Started orchestration with ID = '$InstanceId'" + $Orchestrator = New-OrchestrationCheckStatusResponse -Request $Request -InstanceId $InstanceId + do { + $StillRunning = Get-Item "SecurityBaselines_All\CurrentlyRunning.txt" -ErrorAction SilentlyContinue | Where-Object -Property LastWriteTime -GT (Get-Date).AddHours(-24) + if (!$StillRunning) { + $Results = Get-Content "SecurityBaselines_All\Results.json" -ErrorAction SilentlyContinue + } + else { + Start-Sleep -Milliseconds 500 + } + } while ($StillRunning) +} + + + +Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{ + StatusCode = [HttpStatusCode]::OK + Body = $results + }) \ No newline at end of file diff --git a/Standards_AuditLog/run.ps1 b/Standards_AuditLog/run.ps1 index 965eb974da76..a9254a26dbca 100644 --- a/Standards_AuditLog/run.ps1 +++ b/Standards_AuditLog/run.ps1 @@ -6,6 +6,10 @@ try { $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($tenant)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection -ErrorAction Continue Import-PSSession $session -ea Silentlycontinue -AllowClobber -CommandName "Set-AdminAuditLogConfig", "Get-OrganizationConfig", "Enable-OrganizationCustomization" + $DehydratedTenant = (Get-OrganizationConfig).IsDehydrated + if ($DehydratedTenant) { + Enable-OrganizationCustomization + } Set-AdminAuditLogConfig -UnifiedAuditLogIngestionEnabled $true Get-PSSession | Remove-PSSession Log-request -API "Standards" -tenant $tenant -message "Unified Audit Log Enabled." -sev Info diff --git a/Standards_AutoExpandArchive/function.json b/Standards_AutoExpandArchive/function.json new file mode 100644 index 000000000000..2d4ea9094b24 --- /dev/null +++ b/Standards_AutoExpandArchive/function.json @@ -0,0 +1,9 @@ +{ + "bindings": [ + { + "name": "tenant", + "direction": "in", + "type": "activityTrigger" + } + ] +} \ No newline at end of file diff --git a/Standards_AutoExpandArchive/run.ps1 b/Standards_AutoExpandArchive/run.ps1 new file mode 100644 index 000000000000..a42ac6ab92ec --- /dev/null +++ b/Standards_AutoExpandArchive/run.ps1 @@ -0,0 +1,16 @@ +param($tenant) + +try { + $upn = "notRequired@required.com" + $tokenvalue = ConvertTo-SecureString (Get-GraphToken -AppID 'a0c73c16-a7e3-4564-9a95-2bdf47383716' -RefreshToken $ENV:ExchangeRefreshToken -Scope 'https://outlook.office365.com/.default' -Tenantid $tenant).Authorization -AsPlainText -Force + $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) + $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($tenant)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection -ErrorAction Continue + Import-PSSession $session -ea Silentlycontinue -AllowClobber -CommandName "Set-OrganizationConfig", + Set-OrganizationConfig -AutoExpandingArchive + Get-PSSession | Remove-PSSession + Log-request -API "Standards" -tenant $tenant -message "Added Auto Expanding Archive." -sev Info + +} +catch { + Log-request -API "Standards" -tenant $tenant -message "Failed to apply Auto Expanding Archives Error: $($_.exception.message)" -sev Error +} \ No newline at end of file diff --git a/Standards_DelegateSentItems/run.ps1 b/Standards_DelegateSentItems/run.ps1 index a6ae31a2962e..f13b0a3a7537 100644 --- a/Standards_DelegateSentItems/run.ps1 +++ b/Standards_DelegateSentItems/run.ps1 @@ -6,7 +6,7 @@ try { $credential = New-Object System.Management.Automation.PSCredential($upn, $tokenValue) $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri "https://ps.outlook.com/powershell-liveid?DelegatedOrg=$($Tenant)&BasicAuthToOAuthConversion=true" -Credential $credential -Authentication Basic -AllowRedirection -ErrorAction Continue Import-PSSession $session -ea Silentlycontinue -AllowClobber -CommandName "Get-Mailbox", "Set-mailbox" - Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox | set-mailbox -erroraction SilentlyContinue -MessageCopyForSentAsEnabled $true -MessageCopyForSendOnBehalfEnabled $true + Get-Mailbox -ResultSize Unlimited -RecipientTypeDetails UserMailbox, SharedMailbox | Where-Object { $_.MessageCopyForSendOnBehalfEnabled -eq $false -or $_.MessageCopyForSentAsEnabled -eq $false } | set-mailbox -erroraction SilentlyContinue -MessageCopyForSentAsEnabled $true -MessageCopyForSendOnBehalfEnabled $true Get-PSSession | Remove-PSSession Log-request -API "Standards" -tenant $tenant -message "Delegate Sent Items Style enabled." -sev Info } diff --git a/Standards_LegacyMFA/run.ps1 b/Standards_LegacyMFA/run.ps1 index c77e642671a2..4b2fac6b5d80 100644 --- a/Standards_LegacyMFA/run.ps1 +++ b/Standards_LegacyMFA/run.ps1 @@ -1,8 +1,24 @@ param($tenant) try { - #nothing yet. + $AADGraphtoken = (Get-GraphToken -scope "https://graph.windows.net/.default") + $tenantid = ((Get-Content "tenants.cache.json" | ConvertFrom-Json) | Where-Object -Property DefaultDomainName -EQ $tenant).CustomerID + $TrackingGuid = New-Guid + $LogonPost = @" +http://provisioning.microsoftonline.com/IProvisioningWebService/MsolConnecturn:uuid:$TrackingGuidhttp://www.w3.org/2005/08/addressing/anonymous$($AADGraphtoken['Authorization'])50afce61-c917-435b-8c6d-60aa5a8b8aa71.2.183.57Version47$($TrackingGuid)https://provisioningapi.microsoftonline.com/provisioningwebservice.svcVersion4 +"@ + $DataBlob = (Invoke-RestMethod -Method POST -Uri "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" -ContentType "application/soap+xml; charset=utf-8" -Body $LogonPost).envelope.header.BecContext.DataBlob.'#text' + $UserListXML = @" +http://provisioning.microsoftonline.com/IProvisioningWebService/ListUsersurn:uuid:$TrackingGuidhttp://www.w3.org/2005/08/addressing/anonymous$($AADGraphtoken['Authorization'])$DataBlob250afce61-c917-435b-8c6d-60aa5a8b8aa71.2.183.57Version474e6cb653-c968-4a3a-8a11-2c8919218aebhttps://provisioningapi.microsoftonline.com/provisioningwebservice.svcVersion16$($tenantid)500AscendingNone +"@ + $Users = (Invoke-RestMethod -Uri "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" -Method post -Body $UserListXML -ContentType 'application/soap+xml; charset=utf-8').envelope.body.ListUsersResponse.listusersresult.returnvalue.results.user | Where-Object { ($_.StrongAuthenticationRequirements.StrongAuthenticationRequirement.state -eq $null) } + foreach ($user in $users) { + $MSOLXML = @" +http://provisioning.microsoftonline.com/IProvisioningWebService/SetUserurn:uuid:$TrackingGuidhttp://www.w3.org/2005/08/addressing/anonymous$($AADGraphtoken['Authorization'])$($DataBlob)9450afce61-c917-435b-8c6d-60aa5a8b8aa71.2.183.57Version47$TrackingGuidhttps://provisioningapi.microsoftonline.com/provisioningwebservice.svcVersion16$($tenantid)$($User.ObjectID)*0001-01-01T00:00:00Enabled +"@ + $SetMFA = (Invoke-RestMethod -Uri "https://provisioningapi.microsoftonline.com/provisioningwebservice.svc" -Method post -Body $MSOLXML -ContentType 'application/soap+xml; charset=utf-8') + } } catch { - Log-request "Standards API: $($tenant) failed to apply mailbox retention. Error: $($exception.message)" -sev Error + Log-request "Standards API: $($tenant) failed to apply per user MFA. Error: $($exception.message)" -sev Error } \ No newline at end of file diff --git a/version_latest.txt b/version_latest.txt index e2cac26c1a82..589268e6fedb 100644 --- a/version_latest.txt +++ b/version_latest.txt @@ -1 +1 @@ -1.2.3 \ No newline at end of file +1.3.0 \ No newline at end of file