Forum Discussion

vrush96's avatar
vrush96
Copper Contributor
Aug 29, 2024
Solved

Facing error when running a ps script using mggraph

Hi Community,

 

I am facing issue in fetching lastsignindate from azure ad using mggraph it returns error of 404 not found whereas user is present in azure ad.

 

The script i am sharing has some fields blank for security reasons:

# Function to authenticate with Microsoft Graph
function Get-GraphToken {
param (
[string]$tenantId,
[string]$clientId,
[string]$clientSecret,
[string]$authUrl
)
$authBody = @{
grant_type = "client_credentials"
scope = "https://graph.microsoft.com/.default"
client_id = $clientId
client_secret = $clientSecret
}
try {
$tokenResponse = Invoke-RestMethod -Method Post -Uri $authUrl -ContentType "application/x-www-form-urlencoded" -Body $authBody
return $tokenResponse.access_token
} catch {
Write-Error "Failed to authenticate with Microsoft Graph: $_"
return $null
}
}
# Function to get the most recent LastLogon attribute from all domain controllers
function Get-LastLogon {
param (
[string]$userName
)
$dcs = Get-ADDomainController -Filter * | Select-Object -ExpandProperty HostName
$lastLogon = 0
foreach ($dc in $dcs) {
try {
$user = Get-ADUser $userName -Server $dc -Properties LastLogon
if ($user.LastLogon -gt $lastLogon) {
$lastLogon = $user.LastLogon
}
} catch {
Write-Error "Failed to retrieve LastLogon from $dc for $userName $_"
}
}
if ($lastLogon -ne 0) {
return [DateTime]::FromFileTime($lastLogon)
} else {
return $null
}
}
# Function to get last sign-in date from Azure AD using User ID
function Get-UserLastSignIn {
param (
[string]$userId,
[hashtable]$headers
)
try {
# Get the user's sign-in activity using userId
$userInfo = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users/$userId?$select=signInActivity" -Headers $headers
if ($userInfo.signInActivity -and $userInfo.signInActivity.lastSignInDateTime) {
# Return the lastSignInDateTime
return [DateTime]::Parse($userInfo.signInActivity.lastSignInDateTime)
} else {
Write-Warning "No sign-in activity available for user with ID $userId."
return $null
}
} catch {
Write-Error "Failed to retrieve sign-in data for user with ID $userId $_"
return $null
}
}
# Function to send notification
function Send-Notification {
param (
[string]$userEmail,
[string]$managerEmail
)
$subject = "Login Reminder"
$body = "You have not logged in for the past 10 days. Please log in to avoid account deactivation."
# Uncomment the below line to send the actual email
# Send-MailMessage -From "" -To $userEmail -Cc $managerEmail -Subject $subject -Body $body -SmtpServer $smtpServer
}
# Function to create and send the HTML report
function Create-And-Send-HTMLReport {
param (
[array]$csvData,
[string]$htmlReportPath
)
$htmlContent = @"
<html>
<head>
<title>User Login Report</title>
<style>
table {
width: 100%;
border-collapse: collapse;
}
table, th, td {
border: 1px solid black;
}
th, td {
padding: 8px;
text-align: left;
}
</style>
</head>
<body>
<h2>User Login Report</h2>
<table>
<tr>
<th>samAccountName</th>
<th>DisplayName</th>
<th>MailSentToManager</th>
<th>LastLogonOnPrem</th>
<th>LastLogonAzureAD</th>
<th>SessionRevoked</th>
<th>Action</th>
</tr>
"@
foreach ($row in $csvData) {
$htmlContent += "<tr>"
$htmlContent += "<td>$($row.samAccountName)</td>"
$htmlContent += "<td>$($row.DisplayName)</td>"
$htmlContent += "<td>$($row.MailSentToManager)</td>"
$htmlContent += "<td>$($row.LastLogonOnPrem)</td>"
$htmlContent += "<td>$($row.LastLogonAzureAD)</td>"
$htmlContent += "<td>$($row.SessionRevoked)</td>"
$htmlContent += "<td>$($row.Action)</td>"
$htmlContent += "</tr>"
}
$htmlContent += @"
</table>
</body>
</html>
"@
# Save the HTML content to a file
$htmlContent | Out-File -FilePath $htmlReportPath -Encoding UTF8
# Uncomment the below line to send the actual email
# Send-MailMessage -From "" -To "" -Subject "Daily User Login HTML Report" -BodyAsHtml -Body $htmlContent -SmtpServer $smtpServer
}
# Function to send daily report to IT
function Send-DailyReport {
param (
[string]$reportPath
)
$subject = "Daily User Login Report"
$body = Get-Content -Path $reportPath -Raw
# Uncomment the below line to send the actual email
# Send-MailMessage -From "" -To "" -Subject $subject -Body $body -BodyAsHtml -SmtpServer $smtpServer -Port $smtpPort
}
# Main script starts here
# Define variables
$tenantId = ""
$clientSecret = ""
$clientId = ""
$authUrl = "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token"
$smtpServer = ""
$smtpPort =
$departmentsFilePath = "C:\psscr\Departments.txt"
# Authenticate with Microsoft Graph
$token = Get-GraphToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret -authUrl $authUrl
# Ensure that the token was successfully obtained
if (-not $token) {
Write-Error "Failed to obtain Microsoft Graph token. Exiting script."
exit
}
$headers = @{ Authorization = "Bearer $token" }
# Set cut-off dates
$cutOffDate10Days = (Get-Date).AddDays(-10)
$cutOffDate15Days = (Get-Date).AddDays(-15)
# Check departments
$departments = Get-Content -Path $departmentsFilePath
# Initialize CSV report
$currentDateTime = (Get-Date).ToString("dd-MM-yyyy_HH-mm")
$csvFilePath = "C:\psscr\DailyUserLoginReport_$currentDateTime.csv"
$htmlReportPath = "C:\psscr\DailyUserLoginReport_$currentDateTime.html"
$csvData = @()
# Process each department
foreach ($dept in $departments) {
$users = Get-ADUser -Filter { Department -eq $dept } -Properties LastLogonTimestamp, Manager, Enabled, UserPrincipalName, DisplayName
foreach ($user in $users) {
if (-not $user.Enabled) {
continue
}
# Get the most recent LastLogon from AD
$lastLogon = Get-LastLogon -userName $user.SamAccountName
$lastLogonString = if ($lastLogon) { $lastLogon.ToString("yyyy-MM-dd HH:mm:ss") } else { "Never" }
# Get the user's Azure AD ID
$userResponse = Invoke-RestMethod -Uri "https://graph.microsoft.com/v1.0/users?$filter=userPrincipalName eq '$($user.UserPrincipalName)'" -Headers $headers
# Find the user with the exact UserPrincipalName match
$userId = $null
foreach ($responseUser in $userResponse.value) {
if ($responseUser.userPrincipalName -eq $user.UserPrincipalName) {
$userId = $responseUser.id
break
}
}
#$userId = $userResponse.value[$user.UserPrincipalName].id
# Ensure that a valid userId was retrieved
if ($null -eq $userId) {
Write-Warning "Could not retrieve userId for $($user.UserPrincipalName). Skipping..."
continue
}
# Get the most recent last sign-in date from Azure AD using ID
$lastSignInDate = Get-UserLastSignIn -userId $userId -headers $headers
$lastSignInDateString = if ($lastSignInDate) { $lastSignInDate.ToString("yyyy-MM-dd HH:mm:ss") } else { "Never" }
$action = ""
$mailSent = $false
$sessionRevoked = $false
if ($lastLogon -lt $cutOffDate10Days -and $lastSignInDate -lt $cutOffDate10Days) {
# Send notification to the user and manager
$manager = Get-ADUser -Identity $user.Manager -Properties EmailAddress
Send-Notification -userEmail $user.EmailAddress -managerEmail $manager.EmailAddress
$mailSent = $true
}
if ($lastLogon -lt $cutOffDate15Days -and $lastSignInDate -lt $cutOffDate15Days) {
# Revoke Azure AD sessions and disable the on-premises AD account
# Uncomment the below line to revoke Azure AD sessions
# Invoke-RestMethod -Method Post -Uri "https://graph.microsoft.com/v1.0/users/$userId/revokeSignInSessions" -Headers $headers
#Disable-ADAccount -Identity $user.SamAccountName
$action = "Account Disabled"
$sessionRevoked = $true
}
$csvData += [pscustomobject]@{
samAccountName = $user.SamAccountName
DisplayName = $user.DisplayName
MailSentToManager = $mailSent
LastLogonOnPrem = $lastLogonString
LastLogonAzureAD = $lastSignInDateString
SessionRevoked = $sessionRevoked
Action = $action
}
}
}
# Export to CSV
$csvData | Export-Csv -Path $csvFilePath -NoTypeInformation
# Create and send the HTML report
Create-And-Send-HTMLReport -csvData $csvData -htmlReportPath $htmlReportPath
# Send the daily report to IT
Send-DailyReport -reportPath $htmlReportPath

Any help is appreciated why this error occurs is known to us that it is not found will this need changes in script or something else. The permissions given to Azure app is correct as is does not show permission error when running the script.

  • vrush96 

     

    Hi.

     

    Your second and third Invoke-RestMethod URIs don't look properly formatted to me, going off what you've pasted in this thread.

     

    Because the strings are using double-quotes, PowerShell will parse breakout characters like $ and ? instead of interpreting them literally.

     

    I haven't bothered running a real test as there's no need. In the examples below, the only thing we're interested in is the parsed URI in yellow. Here, you can see that based on your code, you're not going to get the URI you think you're getting (first example) unless you remember to escape the special characters (second example).

     

     

     

    Cheers,

    Lain

  • LainRobertson's avatar
    LainRobertson
    Silver Contributor

    vrush96 

     

    Hi.

     

    Your second and third Invoke-RestMethod URIs don't look properly formatted to me, going off what you've pasted in this thread.

     

    Because the strings are using double-quotes, PowerShell will parse breakout characters like $ and ? instead of interpreting them literally.

     

    I haven't bothered running a real test as there's no need. In the examples below, the only thing we're interested in is the parsed URI in yellow. Here, you can see that based on your code, you're not going to get the URI you think you're getting (first example) unless you remember to escape the special characters (second example).

     

     

     

    Cheers,

    Lain

    • vrush96's avatar
      vrush96
      Copper Contributor
      I tried to escape the special characters still i get 404 error
      • LainRobertson's avatar
        LainRobertson
        Silver Contributor

        vrush96 

         

        Perhaps use the -Verbose parameter as I have in the examples above on the second and third Invoke-RestMethod calls so you can see what the final URI looks like and validate that it matches the required format.

         

        Until we know for sure that the URI is valid, there's no point in looking any further.

         

        Cheers,

        Lain

Resources