Skip to content

Commit

Permalink
Implemented querying of user usage to find part-time employee types.
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisjantzen committed Jul 22, 2022
1 parent 45ee018 commit 6953e6a
Show file tree
Hide file tree
Showing 4 changed files with 347 additions and 177 deletions.
16 changes: 16 additions & 0 deletions User Audit - Constants.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,22 @@ $EmailOnlyGroupsIgnore = @(
#
$InactivityO365Preference = $false

####################
# $PartTimeEmployeesByUsage
#
# Get usage info from the Device Audit DB and use usage stats to determine which employee's might be part-time employee's and who is full-time.
# The $Device_DB_APIKey and $Device_DB_APIEndpoint variables must also be configured.
# This will only work if the user has a minimum of 1 full month of device audit usage data, if any less it will just consider the user a regular employee.
#
$PartTimeEmployeesByUsage = $false

####################
# $PartTimePercentage
#
# Any usage below this percent will be considered part-time, anything equals and above, full-time
#
$PartTimePercentage = 70

####################
# $TotalsByLocation
#
Expand Down
259 changes: 168 additions & 91 deletions User Audit.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2228,6 +2228,136 @@ $matchingPath = "C:\billing_audit\contacts.json"
$matchingJson | Out-File -FilePath $matchingPath
Write-Host "Exported a contact matching json file."

# Update the Device Audit DB if applicable and get usage data if applicable
$UserUsage = @()
if ($FullMatches -and $Device_DB_APIKey -and $Device_DB_APIEndpoint) {
If (Get-Module -ListAvailable -Name "Az.Accounts") {Import-module Az.Accounts } Else { install-module Az.Accounts -Force; import-module Az.Accounts }
If (Get-Module -ListAvailable -Name "Az.Resources") {Import-module Az.Resources } Else { install-module Az.Resources -Force; import-module Az.Resources }
#If (Get-Module -ListAvailable -Name "CosmosDB") {Import-module CosmosDB} Else { install-module CosmosDB -Force; import-module CosmosDB}

$headers = @{
'x-api-key' = $Device_DB_APIKey
}
$body = @{
'tokenType' = 'users'
}

$Token = Invoke-RestMethod -Method Post -Uri $Device_DB_APIEndpoint -Headers $headers -Body ($body | ConvertTo-Json) -ContentType 'application/json'
if ($Token) {

$collectionId = Get-CosmosDbCollectionResourcePath -Database 'DeviceUsage' -Id 'Users'
$contextToken = New-CosmosDbContextToken `
-Resource $collectionId `
-TimeStamp (Get-Date $Token.Timestamp) `
-TokenExpiry $Token.Life `
-Token (ConvertTo-SecureString -String $Token.Token -AsPlainText -Force)

$DB_Name = 'DeviceUsage'
$CustomerAcronym = $Device_DB_APIKey.split('.')[0]
$CosmosDBAccount = "stats-$($CustomerAcronym)".ToLower()
$resourceContext = New-CosmosDbContext -Account $CosmosDBAccount -Database $DB_Name -Token $contextToken

if ($resourceContext) {
$Query = "SELECT * FROM Users u"
$ExistingUsers = Get-CosmosDbDocument -Context $resourceContext -Database $DB_Name -CollectionId "Users" -Query $Query -PartitionKey 'user'

if ($ExistingUsers) {
$Now_UTC = Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z'
$UserCount = ($ExistingUsers | Measure-Object).Count
$i = 0
foreach ($User in $ExistingUsers) {
$i++
$Match = $FullMatches | Where-Object { $_.'AD-Username' -eq $User.Username } | Select-Object -First 1

[int]$PercentComplete = ($i / $UserCount * 100)
Write-Progress -Activity "Updating users in Device Audit Database." -PercentComplete $PercentComplete -Status ("Working - " + $PercentComplete + "% (Updating: $($User.Username))")

if (!$Match) {
$EscapedUsername = [Regex]::Escape($User.Username)
$Match = $FullMatches | Where-Object { $_.'ITG-Notes' -match ".*(Username: " + $EscapedUsername + "(\s|W|$)).*" } | Select-Object -First 1

if (!$Match -and $User.DomainOrLocal -eq "Local") {
$Match = $FullMatches | Where-Object { $_.'ITG-Notes' -match ".*(Local Account: " + $EscapedUsername + "(\s|W|$)).*" } | Select-Object -First 1
}

# Still no match, fall back to email-only search
if (!$Match) {
$Match = $FullMatches | Where-Object { $_.'O365-PrimarySmtp' -match "$EscapedUsername\.user@" -or $_.'ITG-Notes' -match ".*(Primary O365 Email: " + $EscapedUsername + "@).*" } | Select-Object -First 1
}
}

$UpdateRequired = $false
# If changing fields, update in device audit as well
$UpdatedUser = $User | Select-Object Id, Domain, DomainOrLocal, Username, LastUpdated, type, O365Email, ITG_ID, ADUsername

if ($Match) {
if ($Match.'AD-Username' -and $User.ADUsername -ne $Match.'AD-Username') {
$UpdatedUser.ADUsername = $Match.'AD-Username'
$User.ADUsername = $Match.'AD-Username'
$UpdateRequired = $true
}
if ($Match.'O365-PrimarySmtp' -and $User.O365Email -ne $Match.'O365-PrimarySmtp') {
$UpdatedUser.O365Email = $Match.'O365-PrimarySmtp'
$User.O365Email = $Match.'O365-PrimarySmtp'
$UpdateRequired = $true
}
if ($Match.id -and $User.ITG_ID -ne $Match.id) {
$UpdatedUser.ITG_ID = $Match.id
$User.ITG_ID = $Match.id
$UpdateRequired = $true
}
}

$UserID = $User.Id
if ($UpdateRequired) {
$UpdatedUser.LastUpdated = $Now_UTC
$User.LastUpdated = $Now_UTC
Set-CosmosDbDocument -Context $resourceContext -Database $DB_Name -CollectionId "Users" -Id $UserID -DocumentBody ($UpdatedUser | ConvertTo-Json) -PartitionKey 'user' | Out-Null
}
}
Write-Progress -Activity "Updating users in Device Audit Database." -Status "Ready" -Completed
}
}

# Get user usage info from Device Audit DB if configured to look for part time employees
if ($PartTimeEmployeesByUsage -and $PartTimeEmployeesByUsage -and $ExistingUsers) {
$body = @{
'tokenType' = 'userusage'
}
$Token2 = Invoke-RestMethod -Method Post -Uri $Device_DB_APIEndpoint -Headers $headers -Body ($body | ConvertTo-Json) -ContentType 'application/json'

if ($Token2) {
$collectionId2 = Get-CosmosDbCollectionResourcePath -Database 'DeviceUsage' -Id 'UserUsage'
$contextToken2 = New-CosmosDbContextToken `
-Resource $collectionId2 `
-TimeStamp (Get-Date $Token2.Timestamp) `
-TokenExpiry $Token2.Life `
-Token (ConvertTo-SecureString -String $Token2.Token -AsPlainText -Force)
$resourceContext2 = New-CosmosDbContext -Account $CosmosDBAccount -Database $DB_Name -Token $contextToken2

if ($resourceContext2) {
$Query2 = "SELECT * FROM UserUsage AS uu"
$UserUsage = Get-CosmosDbDocument -Context $resourceContext2 -Database $DB_Name -CollectionId "UserUsage" -Query $Query2 -QueryEnableCrossPartition $true

if ($UserUsage) {
foreach ($Usage in $UserUsage) {
$ExistingUser = $ExistingUsers | Where-Object { $_.Id -eq $Usage.Id }
$Usage | Add-Member -MemberType NoteProperty -Name User -Value $null
if ($ExistingUser) {
$Usage.User = $ExistingUser
}
}
}
}
}
}
}
}

if (!$UserUsage -or ($UserUsage | Measure-Object).Count -le 0) {
$PartTimeEmployeesByUsage = $false
}


#############################################
##### Matches Made. Find Discrepancies. #####
Expand All @@ -2251,6 +2381,21 @@ if ($FullMatches) {
$IgnoreWarnings += $IgnoreTypes
}

$PartTimeUsage = $false
if ($PartTimeEmployeesByUsage) {
$UsageStats = $UserUsage | Where-Object { $_.User.ITG_ID -eq $MatchID }
if ($UsageStats) {
$LastMonthDate = Get-Date (Get-Date).AddMonths(-1) -Format "yyyy-MM"
$TwoMonthsAgoDate = Get-Date (Get-Date).AddMonths(-2) -Format "yyyy-MM"
$LastMonthUsage = $UsageStats.DaysActive.HistoryPercent.$LastMonthDate
$TwoMonthsAgoUsage = $UsageStats.DaysActive.HistoryPercent.$TwoMonthsAgoDate

if ($LastMonthUsage -and $LastMonthUsage -lt $PartTimePercentage -and $LastMonthUsage -gt 0 -and (!$TwoMonthsAgoUsage -or $TwoMonthsAgoUsage -lt $PartTimePercentage)) {
$PartTimeUsage = $true
}
}
}

###########
# AD Checks
if ($CheckAD) {
Expand Down Expand Up @@ -2303,16 +2448,24 @@ if ($FullMatches) {
# MaybeTerminate
$WarnObj.type = "MaybeTerminate"
$WarnObj.reason = "AD Account Unused. Maybe disable it? Please review. (Last login > 150 days ago.)"
if ($ADMatch.LastLogonDate) {
$LastLoginDaysAgo = (New-TimeSpan -Start $ADMatch.LastLogonDate -End (Get-Date)).Days
if ($ADMatch.LastLogonDate -lt (Get-Date).AddDays(-150)) { $WarnObj.reason += " (Last Login: $($LastLoginDaysAgo) days ago)" }
} else {
$WarnObj.reason += " (Last Login: Never)"
}
# If $InactivityO365Preference is $true, this gets skipped and will only be checked in the O365 section if the O365 account is inactive
} elseif ($ContactType -eq 'Terminated' -and 'ToEnabled' -notin $IgnoreWarnings) {
# ToEnabled
$WarnObj.type = "ToEnabled"
$WarnObj.reason = "AD Account Enabled. IT Glue Contact should not be 'Terminated'."
} elseif ($ContactType -notlike "Employee - Part Time" -and ($ADMatch.Description -like "*part?time*" -or $ADMatch.Description -like "*casual*" -or
$ADMatch.Title -like "*part?time*" -or $ADMatch.Title -like "*casual*") -and !$EmailOnly -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
} elseif ($ContactType -notlike "Employee - Part Time" -and $ContactType -notlike "Shared Account" -and $ContactType -notlike "Employee - Multi User" -and
($ADMatch.Description -like "*part?time*" -or $ADMatch.Description -like "*casual*" -or
$ADMatch.Title -like "*part?time*" -or $ADMatch.Title -like "*casual*" -or $PartTimeUsage) -and !$EmailOnly -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
# ToEmployeePartTime
$WarnObj.type = "ToEmployeePartTime"
$WarnObj.reason = "AD account appears to be part time. Consider changing the IT Glue Contact type to 'Employee - Part Time'."
if ($PartTimeUsage) { $WarnObj.reason += " (Last Months Usage: $($LastMonthUsage)% [$($UsageStats.DaysActive.LastMonth) days])" }
} elseif ($ContactType -notlike "Contractor" -and ($ADMatch.Description -like "*contract*" -or $ADMatch.Title -like "*contract*") -and !$EmailOnly -and 'ToContractor' -notin $IgnoreWarnings) {
# ToContractor
$WarnObj.type = "ToContractor"
Expand Down Expand Up @@ -2424,6 +2577,12 @@ if ($FullMatches) {
# MaybeTerminate
$WarnObj.type = "MaybeTerminate"
$WarnObj.reason = "$EmailType Account Unused. Maybe disable it? Please review. (Last login > 150 days ago.)"
if ($O365Match.LastUserActionTime) {
$LastLoginDaysAgo = (New-TimeSpan -Start $O365Match.LastUserActionTime -End (Get-Date)).Days
if ($O365Match.LastUserActionTime -lt (Get-Date).AddDays(-150)) { $WarnObj.reason += " (Last Login: $($LastLoginDaysAgo) days ago)" }
} else {
$WarnObj.reason += " (Last Login: Never)"
}
} elseif ($InactivityO365Preference -and $CheckInactivity -and $ContactType -ne 'Terminated' -and $HasAD -and (!$ADEnabled -or ($ADMatch.ad.LastLogonDate -and $ADMatch.ad.LastLogonDate -lt (Get-Date).AddDays(-150))) -and (($O365Match.LastUserActionTime -and $O365Match.LastUserActionTime -lt (Get-Date).AddDays(-150)) -or !$O365Match.LastUserActionTime) -and $ContactType -ne "Employee - On Leave" -and 'MaybeTerminate' -notin $IgnoreWarnings) {
# MaybeTerminate
$WarnObj.type = "MaybeTerminate"
Expand Down Expand Up @@ -2456,12 +2615,13 @@ if ($FullMatches) {
} else {
$WarnObj.reason = "$EmailType account has no associated AD account. Consider changing the IT Glue Contact type to 'Employee - Email Only'. Alternatively: 'External User' or 'Internal / Shared Mailbox'."
}
} elseif ($ContactType -notlike "Employee - Part Time" -and $ContactType -notlike "Employee - Email Only" -and
} elseif ($ContactType -notlike "Employee - Part Time" -and $ContactType -notlike "Employee - Email Only" -and $ContactType -notlike "Shared Account" -and $ContactType -notlike "Employee - Multi User" -and
($O365Match.DisplayName -like "*part?time*" -or $O365Match.DisplayName -like "*casual*" -or
$O365Match.Title -like "*part?time*" -or $O365Match.Title -like "*casual*") -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
$O365Match.Title -like "*part?time*" -or $O365Match.Title -like "*casual*" -or $PartTimeUsage) -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
# ToEmployeePartTime
$WarnObj.type = "ToEmployeePartTime"
$WarnObj.reason = "$EmailType account appears to be part time. Consider changing the IT Glue Contact type to 'Employee - Part Time'."
if ($PartTimeUsage) { $WarnObj.reason += " (Last Months Usage: $($LastMonthUsage)% [$($UsageStats.DaysActive.LastMonth) days])" }
} elseif ($ContactType -notlike "Contractor" -and $ContactType -notlike "Employee - Email Only" -and
($O365Match.DisplayName -like "*contract*" -or $O365Match.Title -like "*contract*") -and 'ToContractor' -notin $IgnoreWarnings) {
# ToContractor
Expand Down Expand Up @@ -2524,11 +2684,13 @@ if ($FullMatches) {
# ImproperlyTerminated
$WarnObj.type = "ImproperlyTerminated"
$WarnObj.reason = "ITG contact notes list 'Disabled', yet this account is not terminated. Please review and fix."
} elseif ($ContactType -notlike "Employee - Part Time" -and ($Contact.notes -like "*part?time*" -or $Contact.notes -like "*casual*" -or
$Contact.title -like "*part?time*" -or $Contact.title -like "*casual*") -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
} elseif ($ContactType -notlike "Employee - Part Time" -and $ContactType -notlike "Shared Account" -and $ContactType -notlike "Employee - Multi User" -and
($Contact.notes -like "*part?time*" -or $Contact.notes -like "*casual*" -or
$Contact.title -like "*part?time*" -or $Contact.title -like "*casual*" -or $PartTimeUsage) -and 'ToEmployeePartTime' -notin $IgnoreWarnings) {
# ToEmployeePartTime
$WarnObj.type = "ToEmployeePartTime"
$WarnObj.reason = "ITG account appears to be part time. Consider changing the IT Glue Contact type to 'Employee - Part Time'."
if ($PartTimeUsage) { $WarnObj.reason += " (Last Months Usage: $($LastMonthUsage)% [$($UsageStats.DaysActive.LastMonth) days])" }
} elseif ($ContactType -notlike "Contractor" -and ($Contact.notes -like "*contract*" -or $Contact.title -like "*contract*") -and 'ToContractor' -notin $IgnoreWarnings) {
# ToContractor
$WarnObj.type = "ToContractor"
Expand Down Expand Up @@ -2744,91 +2906,6 @@ $historyPath = "C:\billing_history\contacts_$($Month)_$($Year).json"
$historyContacts | Out-File -FilePath $historyPath
Write-Host "Exported a billing history file."

# Update the Device Audit DB if applicable
if ($Device_DB_APIKey -and $Device_DB_APIEndpoint) {
$headers = @{
'x-api-key' = $Device_DB_APIKey
}

$Token = Invoke-RestMethod -Method Post -Uri $Device_DB_APIEndpoint -Headers $headers

if ($Token) {
If (Get-Module -ListAvailable -Name "Az.Accounts") {Import-module Az.Accounts } Else { install-module Az.Accounts -Force; import-module Az.Accounts }
If (Get-Module -ListAvailable -Name "Az.Resources") {Import-module Az.Resources } Else { install-module Az.Resources -Force; import-module Az.Resources }
#If (Get-Module -ListAvailable -Name "CosmosDB") {Import-module CosmosDB} Else { install-module CosmosDB -Force; import-module CosmosDB}

$collectionId = Get-CosmosDbCollectionResourcePath -Database 'DeviceUsage' -Id 'Users'
$contextToken = New-CosmosDbContextToken `
-Resource $collectionId `
-TimeStamp (Get-Date $Token.Timestamp) `
-TokenExpiry $Token.Life `
-Token (ConvertTo-SecureString -String $Token.Token -AsPlainText -Force)

$DB_Name = 'DeviceUsage'
$CustomerAcronym = $Device_DB_APIKey.split('.')[0]
$CosmosDBAccount = "stats-$($CustomerAcronym)".ToLower()
$resourceContext = New-CosmosDbContext -Account $CosmosDBAccount -Database $DB_Name -Token $contextToken

if ($resourceContext) {
$Query = "SELECT * FROM Users u"
$ExistingUsers = Get-CosmosDbDocument -Context $resourceContext -Database $DB_Name -CollectionId "Users" -Query $Query -PartitionKey 'user'

if ($ExistingUsers) {
$Now_UTC = Get-Date (Get-Date).ToUniversalTime() -UFormat '+%Y-%m-%dT%H:%M:%S.000Z'
$UserCount = ($ExistingUsers | Measure-Object).Count
$i = 0
foreach ($User in $ExistingUsers) {
$i++
$Match = $FullMatches | Where-Object { $_.'AD-Username' -eq $User.Username } | Select-Object -First 1

[int]$PercentComplete = ($i / $UserCount * 100)
Write-Progress -Activity "Updating users in Device Audit Database." -PercentComplete $PercentComplete -Status ("Working - " + $PercentComplete + "% (Updating: $($User.Username)")

if (!$Match) {
$EscapedUsername = [Regex]::Escape($User.Username)
$Match = $FullMatches | Where-Object { $_.'ITG-Notes' -match ".*(Username: " + $EscapedUsername + "(\s|W|$)).*" } | Select-Object -First 1

if (!$Match -and $User.DomainOrLocal -eq "Local") {
$Match = $FullMatches | Where-Object { $_.'ITG-Notes' -match ".*(Local Account: " + $EscapedUsername + "(\s|W|$)).*" } | Select-Object -First 1
}

# Still no match, fall back to email-only search
if (!$Match) {
$Match = $FullMatches | Where-Object { $_.'O365-PrimarySmtp' -match "$EscapedUsername\.user@" -or $_.'ITG-Notes' -match ".*(Primary O365 Email: " + $EscapedUsername + "@).*" } | Select-Object -First 1
}
}

$UpdateRequired = $false
# If changing fields, update in device audit as well
$UpdatedUser = $User | Select-Object Id, Domain, DomainOrLocal, Username, LastUpdated, type, O365Email, ITG_ID, ADUsername

if ($Match) {
if ($Match.'AD-Username' -and $User.ADUsername -ne $Match.'AD-Username') {
$UpdatedUser.ADUsername = $Match.'AD-Username'
$UpdateRequired = $true
}
if ($Match.'O365-PrimarySmtp' -and $User.O365Email -ne $Match.'O365-PrimarySmtp') {
$UpdatedUser.O365Email = $Match.'O365-PrimarySmtp'
$UpdateRequired = $true
}
if ($Match.id -and $User.ITG_ID -ne $Match.id) {
$UpdatedUser.ITG_ID = $Match.id
$UpdateRequired = $true
}
}

$UserID = $User.Id
if ($UpdateRequired) {
$UpdatedUser.LastUpdated = $Now_UTC
Set-CosmosDbDocument -Context $resourceContext -Database $DB_Name -CollectionId "Users" -Id $UserID -DocumentBody ($UpdatedUser | ConvertTo-Json) -PartitionKey 'user' | Out-Null
}
}
Write-Progress -Activity "Updating users in Device Audit Database." -Status "Ready" -Completed
}
}
}
}

$ExportChoice = $false
$ExportChoice = [System.Windows.MessageBox]::Show('Would you like to export the full user list showing matched AD/O365 accounts?', 'Export Matched User List', 'YesNo')

Expand Down
Loading

0 comments on commit 6953e6a

Please sign in to comment.