Skip to content

Commit abfabc6

Browse files
authored
Merge pull request #297 from KelvinTegelaar/dev
[pull] dev from KelvinTegelaar:dev
2 parents e3d49b0 + 0d60695 commit abfabc6

File tree

45 files changed

+2188
-404
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2188
-404
lines changed

Modules/CIPPCore/Public/Alerts/Get-CIPPAlertInactiveLicensedUsers.ps1

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,60 @@ function Get-CIPPAlertInactiveLicensedUsers {
88
[Parameter(Mandatory = $false)]
99
[Alias('input')]
1010
$InputValue,
11+
[Parameter(Mandatory = $false)]
12+
[switch]$IncludeNeverSignedIn, # Include users who have never signed in (default is to skip them), future use would allow this to be set in an alert configuration
1113
$TenantFilter
1214
)
1315

1416
try {
1517
try {
18+
$Lookup = (Get-Date).AddDays(-90).ToUniversalTime()
19+
20+
# Build base filter - cannot filter assignedLicenses server-side
21+
$BaseFilter = if ($InputValue -eq $true) { "accountEnabled eq true" } else { "" }
1622

17-
$Lookup = (Get-Date).AddDays(-90).ToUniversalTime().ToString('o')
18-
$GraphRequest = New-GraphGetRequest -uri "https://graph.microsoft.com/beta/users?`$filter=(signInActivity/lastNonInteractiveSignInDateTime le $Lookup)&`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled,assignedLicenses" -scope 'https://graph.microsoft.com/.default' -tenantid $TenantFilter |
19-
Where-Object { $null -ne $_.assignedLicenses.skuId }
23+
$Uri = if ($BaseFilter) {
24+
"https://graph.microsoft.com/beta/users?`$filter=$BaseFilter&`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled,assignedLicenses"
25+
} else {
26+
"https://graph.microsoft.com/beta/users?`$select=id,UserPrincipalName,signInActivity,mail,userType,accountEnabled,assignedLicenses"
27+
}
28+
29+
$GraphRequest = New-GraphGetRequest -uri $Uri -scope 'https://graph.microsoft.com/.default' -tenantid $TenantFilter |
30+
Where-Object { $null -ne $_.assignedLicenses -and $_.assignedLicenses.Count -gt 0 }
2031

21-
# true = only active users
22-
if ($InputValue -eq $true) { $GraphRequest = $GraphRequest | Where-Object { $_.accountEnabled -eq $true } }
2332
$AlertData = foreach ($user in $GraphRequest) {
24-
$Message = 'User {0} has been inactive for 90 days, but still has a license assigned.' -f $user.UserPrincipalName
25-
$user | Select-Object -Property UserPrincipalName, signInActivity, @{Name = 'Message'; Expression = { $Message } }
33+
$lastInteractive = $user.signInActivity.lastSignInDateTime
34+
$lastNonInteractive = $user.signInActivity.lastNonInteractiveSignInDateTime
2635

27-
}
28-
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData
36+
# Find most recent sign-in
37+
$lastSignIn = $null
38+
if ($lastInteractive -and $lastNonInteractive) {
39+
$lastSignIn = if ([DateTime]$lastInteractive -gt [DateTime]$lastNonInteractive) { $lastInteractive } else { $lastNonInteractive }
40+
} elseif ($lastInteractive) {
41+
$lastSignIn = $lastInteractive
42+
} elseif ($lastNonInteractive) {
43+
$lastSignIn = $lastNonInteractive
44+
}
2945

30-
} catch {}
46+
# Check if inactive
47+
$isInactive = (-not $lastSignIn) -or ([DateTime]$lastSignIn -le $Lookup)
48+
# Skip users who have never signed in by default (unless IncludeNeverSignedIn is specified)
49+
if (-not $IncludeNeverSignedIn -and -not $lastSignIn) { continue }
50+
# Only process inactive users
51+
if ($isInactive) {
52+
if (-not $lastSignIn) {
53+
$Message = 'User {0} has never signed in but still has a license assigned.' -f $user.UserPrincipalName
54+
} else {
55+
$daysSinceSignIn = [Math]::Round(((Get-Date) - [DateTime]$lastSignIn).TotalDays)
56+
$Message = 'User {0} has been inactive for {1} days but still has a license assigned. Last sign-in: {2}' -f $user.UserPrincipalName, $daysSinceSignIn, $lastSignIn
57+
}
3158

59+
$user | Select-Object -Property UserPrincipalName, signInActivity, @{Name = 'Message'; Expression = { $Message } }
60+
}
61+
}
3262

63+
Write-AlertTrace -cmdletName $MyInvocation.MyCommand -tenantFilter $TenantFilter -data $AlertData
64+
} catch {}
3365
} catch {
3466
Write-AlertMessage -tenant $($TenantFilter) -message "Failed to check inactive users with licenses for $($TenantFilter): $(Get-NormalizedError -message $_.Exception.message)"
3567
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using namespace System.Net
2+
3+
Function Invoke-AddContact {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint
7+
.ROLE
8+
Exchange.Contact.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
Write-LogMessage -headers $Headers -API $APIName -message 'Accessed this API' -Sev 'Debug'
16+
17+
$ContactObject = $Request.Body
18+
$TenantId = $ContactObject.tenantid
19+
20+
try {
21+
# Prepare the body for New-MailContact cmdlet
22+
$BodyToship = @{
23+
displayName = $ContactObject.displayName
24+
name = $ContactObject.displayName
25+
ExternalEmailAddress = $ContactObject.email
26+
FirstName = $ContactObject.firstName
27+
LastName = $ContactObject.lastName
28+
}
29+
30+
# Create the mail contact first
31+
$NewContact = New-ExoRequest -tenantid $TenantId -cmdlet 'New-MailContact' -cmdParams $BodyToship -UseSystemMailbox $true
32+
33+
# Build SetContactParams efficiently with only provided values
34+
$SetContactParams = @{
35+
Identity = $NewContact.id
36+
}
37+
38+
# Helper to add non-empty values
39+
$PropertyMap = @{
40+
'Title' = $ContactObject.Title
41+
'Company' = $ContactObject.Company
42+
'StreetAddress' = $ContactObject.StreetAddress
43+
'City' = $ContactObject.City
44+
'StateOrProvince' = $ContactObject.State
45+
'PostalCode' = $ContactObject.PostalCode
46+
'CountryOrRegion' = $ContactObject.CountryOrRegion
47+
'Phone' = $ContactObject.phone
48+
'MobilePhone' = $ContactObject.mobilePhone
49+
'WebPage' = $ContactObject.website
50+
}
51+
52+
# Add only non-null/non-empty properties
53+
foreach ($Property in $PropertyMap.GetEnumerator()) {
54+
if (![string]::IsNullOrWhiteSpace($Property.Value)) {
55+
$SetContactParams[$Property.Key] = $Property.Value
56+
}
57+
}
58+
59+
# Update the contact with additional details only if we have properties to set
60+
if ($SetContactParams.Count -gt 1) {
61+
Start-Sleep -Milliseconds 500 # Ensure the contact is created before updating
62+
$null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-Contact' -cmdParams $SetContactParams -UseSystemMailbox $true
63+
}
64+
65+
# Check if we need to update MailContact properties
66+
$needsMailContactUpdate = $false
67+
$MailContactParams = @{
68+
Identity = $NewContact.id
69+
}
70+
71+
# Only add HiddenFromAddressListsEnabled if we're actually hiding from GAL
72+
if ([bool]$ContactObject.hidefromGAL) {
73+
$MailContactParams.HiddenFromAddressListsEnabled = $true
74+
$needsMailContactUpdate = $true
75+
}
76+
77+
# Add MailTip if provided
78+
if (![string]::IsNullOrWhiteSpace($ContactObject.mailTip)) {
79+
$MailContactParams.MailTip = $ContactObject.mailTip
80+
$needsMailContactUpdate = $true
81+
}
82+
83+
# Only call Set-MailContact if we have changes to make
84+
if ($needsMailContactUpdate) {
85+
Start-Sleep -Milliseconds 500 # Ensure the contact is created before updating
86+
$null = New-ExoRequest -tenantid $TenantId -cmdlet 'Set-MailContact' -cmdParams $MailContactParams -UseSystemMailbox $true
87+
}
88+
89+
# Log the result
90+
$Result = "Successfully created contact $($ContactObject.displayName) with email address $($ContactObject.email)"
91+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message $Result -Sev 'Info'
92+
$StatusCode = [HttpStatusCode]::OK
93+
}
94+
catch {
95+
$ErrorMessage = Get-CippException -Exception $_
96+
$Result = "Failed to create contact. $($ErrorMessage.NormalizedError)"
97+
Write-LogMessage -headers $Headers -API $APIName -tenant $TenantId -message $Result -Sev 'Error' -LogData $ErrorMessage
98+
$StatusCode = [HttpStatusCode]::InternalServerError
99+
100+
}
101+
102+
# Associate values to output bindings by calling 'Push-OutputBinding'.
103+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
104+
StatusCode = $StatusCode
105+
Body = @{Results = $Result }
106+
})
107+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using namespace System.Net
2+
3+
Function Invoke-AddContactTemplates {
4+
<#
5+
.FUNCTIONALITY
6+
Entrypoint,AnyTenant
7+
.ROLE
8+
Exchange.ReadWrite
9+
#>
10+
[CmdletBinding()]
11+
param($Request, $TriggerMetadata)
12+
13+
$APIName = $Request.Params.CIPPEndpoint
14+
$Headers = $Request.Headers
15+
Write-LogMessage -Headers $Headers -API $APINAME -message 'Accessed this API' -Sev Debug
16+
Write-Host ($request | ConvertTo-Json -Depth 10 -Compress)
17+
18+
try {
19+
$GUID = (New-Guid).GUID
20+
21+
# Create a new ordered hashtable to store selected properties
22+
$contactObject = [ordered]@{}
23+
24+
# Set name and comments first
25+
$contactObject["name"] = $Request.body.displayName
26+
$contactObject["comments"] = "Contact template created $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')"
27+
28+
# Copy specific properties we want to keep
29+
$propertiesToKeep = @(
30+
"displayName", "firstName", "lastName", "email", "hidefromGAL", "streetAddress", "postalCode",
31+
"city", "state", "country", "companyName", "mobilePhone", "businessPhone", "jobTitle", "website", "mailTip"
32+
)
33+
34+
# Copy each property if it exists
35+
foreach ($prop in $propertiesToKeep) {
36+
if ($null -ne $Request.body.$prop) {
37+
$contactObject[$prop] = $Request.body.$prop
38+
}
39+
}
40+
41+
# Convert to JSON
42+
$JSON = $contactObject | ConvertTo-Json -Depth 10
43+
44+
# Save the template to Azure Table Storage
45+
$Table = Get-CippTable -tablename 'templates'
46+
$Table.Force = $true
47+
Add-CIPPAzDataTableEntity @Table -Entity @{
48+
JSON = "$JSON"
49+
RowKey = "$GUID"
50+
PartitionKey = 'ContactTemplate'
51+
}
52+
53+
Write-LogMessage -Headers $Headers -API $APINAME -message "Created Contact Template $($contactObject.name) with GUID $GUID" -Sev Info
54+
$body = [pscustomobject]@{'Results' = "Created Contact Template $($contactObject.name) with GUID $GUID" }
55+
$StatusCode = [HttpStatusCode]::OK
56+
} catch {
57+
$ErrorMessage = Get-CippException -Exception $_
58+
Write-LogMessage -Headers $Headers -API $APINAME -message "Failed to create Contact template: $($ErrorMessage.NormalizedError)" -Sev Error -LogData $ErrorMessage
59+
$body = [pscustomobject]@{'Results' = "Failed to create Contact template: $($ErrorMessage.NormalizedError)" }
60+
$StatusCode = [HttpStatusCode]::Forbidden
61+
}
62+
63+
# Associate values to output bindings by calling 'Push-OutputBinding'.
64+
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
65+
StatusCode = $StatusCode
66+
Body = $body
67+
})
68+
}

0 commit comments

Comments
 (0)