Skip to main content

Analyze User Profile Photos using Azure Computer Vision API

Author: Joseph Velliah

This script uses Azure Cognitive Service API and Microsoft 365 CLI to analyze user profile pictures and assess whether they meet the standards placed by the organization. It can be customized to ban content within an org channel or collaboration network where employees post pictures, memes, etc.

Prerequisites

note

If you don't already have an Azure Cognitive Services instance and key, create a cognitive service instance and get API key from there.

$resultDir = "Output"
$azureVisionApiInstance = "azure-vision-api-instance-name"
$azureVisionApiKey = "azure-vision-api-key"

$photoRequirements = @{
requirePortrait = $false
allowClipart = $true
allowLinedrawing = $true
allowAdult = $false
allowRacy = $false
allowGory = $false
photoRequirements = @{
requirePortrait = $false
allowClipart = $true
allowLinedrawing = $true
allowAdult = $false
allowRacy = $false
allowGory = $false
forbiddenKeywords = @("cartoon", `
"animal", `
"nude", `
"child", `
"people", `
"group", `
"family", `
"several", `
"crowd", `
"food", `
"restaurant", `
"train", `
"bus", `
"car", `
"airplane", `
"vehicle", `
"platform", `
"station", `
"standing", `
"flying", `
"suitcase", `
"screenshot", `
"newspaper", `
"typography", `
"font", `
"document", `
"sport")
}
}

$requiredProfileProperties = "id,displayName,userPrincipalName"
$global:analysisOutcomes = @()

$executionDir = $PSScriptRoot
$outputDir = "$executionDir/$resultDir"
$outputFilePath = "$outputDir/$(get-date -f yyyyMMdd-HHmmss)-scan-profile-pictures-outcome.csv"

if (-not (Test-Path -Path "$outputDir" -PathType Container)) {
New-Item -ItemType Directory -Path "$outputDir"
Write-Host "Created $outputDir folder..."
}
function AddAnalysisOutcome {
param (
[Parameter(Mandatory = $false)] [string] $UserId,
[Parameter(Mandatory = $false)] [string] $UserPrincipalName,
[Parameter(Mandatory = $false)] [bool] $IsPortraitValid,
[Parameter(Mandatory = $false)] [bool] $IsOnlyOnePersonValid,
[Parameter(Mandatory = $false)] [bool] $IsClipartValid,
[Parameter(Mandatory = $false)] [bool] $IsLineDrawingValid,
[Parameter(Mandatory = $false)] [bool] $IsAdultValid,
[Parameter(Mandatory = $false)] [bool] $IsRacyValid,
[Parameter(Mandatory = $false)] [bool] $IsGoryValid,
[Parameter(Mandatory = $false)] [bool] $IsCelebrity,
[Parameter(Mandatory = $false)] [bool] $IsForbiddenKeywordExist,
[Parameter(Mandatory = $false)] [bool] $IsValidProfilePhoto,
[Parameter(Mandatory = $false)] [string] $Notes
)

$analysisOutcome = New-Object -TypeName PSObject

$analysisOutcome | Add-Member -MemberType NoteProperty -Name "UserId" -Value $UserId
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "UserPrincipalName" -Value $UserPrincipalName
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsPortraitValid" -Value $IsPortraitValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsOnlyOnePersonValid" -Value $IsOnlyOnePersonValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsClipartValid" -Value $IsClipartValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsLineDrawingValid" -Value $IsLineDrawingValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsAdultValid" -Value $IsAdultValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsRacyValid" -Value $IsRacyValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsGoryValid" -Value $IsGoryValid
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsCelebrity" -Value $IsCelebrity
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsForbiddenKeywordExist" -Value $IsForbiddenKeywordExist
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "IsValidProfilePhoto" -Value $IsValidProfilePhoto
$analysisOutcome | Add-Member -MemberType NoteProperty -Name "Notes" -Value $Notes

$global:analysisOutcomes += $analysisOutcome
}

$users = m365 entra user list --properties $requiredProfileProperties -o json | ConvertFrom-Json -AsHashtable
$usersCount = $users.Count
Write-Host "Number of users found : $usersCount"

try {
$token = m365 util accesstoken get --resource https://graph.microsoft.com

$i = 0

for ($i = 0; $i -lt $usersCount; $i++) {
try {
$userId = $users[$i].id
$userPrincipalName = $users[$i].userPrincipalName

$percentComplete = ($i / $usersCount) * 100
Write-Progress -Activity "Analysing" -Status "User : $userId - $userPrincipalName" -PercentComplete $percentComplete

try {
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "image/jpg")
$headers.Add("Authorization", "Bearer $token")
$userPhoto = (Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$userId/photo/`$value" -Headers $headers)

if ($userPhoto) {
try {
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Content-Type", "application/json")
$headers.Add("Ocp-Apim-Subscription-Key", $azureVisionApiKey)

$analysis = (Invoke-RestMethod -Uri ("https://$azureVisionApiInstance.cognitiveservices.azure.com/vision/v3.1/analyze?visualFeatures=Categories,Adult,Tags,Description,Faces,Color,ImageType,Objects&details=Celebrities&language=en") `
-Headers $headers `
-Body ($userPhoto) `
-ContentType "application/octet-stream" `
-Method "Post");

if ($analysis) {
$analysisData = $analysis | ConvertFrom-Json -AsHashtable
$isPortrait = $analysisData.categories.Length -gt 0 ? ($analysisData.categories | Where-Object { $_.name -eq 'people_portrait' }).Length -gt 0 ? $true : $false : $false
$isPortraitValid = $photoRequirements.requirePortrait ? $isPortrait : $true
$isOnlyOnePersonValid = $analysisData.faces.Length -eq 1 ? $true : $false
$isClipartValid = $analysisData.imageType.clipArtType -eq 0 ? $true : $false
$isLineDrawingValid = $analysisData.imageType.lineDrawingType -eq 0 ? $true : $false
$isAdultValid = $photoRequirements.allowAdult ? $true : !$analysisData.adult.isAdultContent
$isRacyValid = $photoRequirements.allowRacy ? $true : !$analysisData.adult.isRacyContent
$isGoryValid = $photoRequirements.allowGory ? $true : !$analysisData.adult.isGoryContent
$isCelebrity = ($analysisData.categories | Where-Object { $_.detail.celebrities.Length -gt 0 }).Length -gt 0 ? $true : $false

$invalidKeywords = @()

foreach ($forbiddenKeyword in $photoRequirements.forbiddenKeywords) {
$isForbiddenKeywordExist = ($analysisData.tags | Where-Object { $_.name -eq $forbiddenKeyword }).Length -gt 0 ? $true : $false

if ($isForbiddenKeywordExist) {
$invalidKeyword = New-Object -TypeName PSObject
$invalidKeyword | Add-Member -MemberType NoteProperty -Name forbiddenKeyword -Value $forbiddenKeyword
$invalidKeywords += $invalidKeyword
}
}

$isForbiddenKeywordExist = $invalidKeywords.Length -gt 0 ? $true : $false

$isValidProfilePhoto = $isPortraitValid `
-and $isOnlyOnePersonValid `
-and $isClipartValid `
-and $isLineDrawingValid `
-and $isAdultValid `
-and $isRacyValid `
-and $isGoryValid `
-and !$isCelebrity `
-and !$isForbiddenKeywordExist;

AddAnalysisOutcome $userId `
$userPrincipalName `
$isPortraitValid `
$isOnlyOnePersonValid `
$isClipartValid `
$isLineDrawingValid `
$isAdultValid `
$isRacyValid `
$isGoryValid `
$isCelebrity `
$isForbiddenKeywordExist `
$isValidProfilePhoto `
"Profile photo available"
}
}
catch {
AddAnalysisOutcome $userId `
$userPrincipalName `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
"Unable to analyze profile photo"
}
}
}
catch {
AddAnalysisOutcome $userId `
$userPrincipalName `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
$false `
"Unable to get profile photo"
}
}
catch {
Write-Host "Unable to get profile details for this user" -ForegroundColor Red
}
}
}
catch {
Write-Host "Unable to get new access token" -ForegroundColor Red
}

$global:analysisOutcomes | Export-Csv -Path "$outputFilePath" -NoTypeInformation
Write-Host "Open $outputFilePath to review analysis outcomes report."
CTRL + M