Virtualization, technology, and random rantings with a focus on Citrix and VMware.

Tag: Report

Script To Monitor Group Membership Changes And Report

Sometimes you do adds to a group for reasons. Maybe you are doing a staged migration of things and you are using a group to do that. Maybe someone wants a report of new users added in the last week. This can help do that for you. To complete this, you just need to run a command or two and generate your starting file to compare to. Then you setup a scheduled task to run on a weekly basis and there you have it! If you have had no changes, then it will not email you. If you do have changes, it will email with attachment and count of number of objects added. This also does this based on the named dates of the files so if you need to run it earlier, you will need to do a copy of the file and change the name to reflect a week earlier, or change the logic to not use the date names.

To get your starting file, you will need to create your folder to store the reports and run the command below in PowerShell with a machine that has the AD module loaded as part of the RSAT tools. You will also need the ScheduledTasks module loaded to create the scheduled tasks via PowerShell.

$outputLocation         = "C:\scripts\logs\GroupChanges"
$domain                 = "DomainOfGroup"
$groupName              = "GroupToMonitor"
$date                   = Get-Date
$dateReport             = ($date).ToString("MMddyyyy")
$outputFileName         = "$dateReport-group-members.csv"


$getCurrentGroupMembership = (Get-ADGroup -Server $domain $groupName | Get-ADGroupMember -Recursive | Select-Object -Unique | Sort | Select-Object SamAccountName)

$getCurrentGroupMembership | Export-CSV -Path $outputLocation\$outputFileName -Append -NoTypeInformation

Second verse, somewhat similar to the first.

# Script to do a weekly compare of Active Directory group memberships. Requires AD module to be loaded and an account / service account with read permissions to the objects.

$outputLocation         = "C:\scripts\logs\GroupChanges"
$domain                 = "DomainOfGroup"
$groupName              = "GroupToMonitor"
$date                   = Get-Date
$dateLastWeek           = ($date).AddDays(-7).ToString("MMddyyyy")
$dateReport             = ($date).ToString("MMddyyyy")
$outputFileName         = "$dateReport-group-members.csv"
$importFileNameLastWeek = "$dateLastWeek-group-members.csv"
$deltaFileName          = "$dateReport-delta.csv"
$report                 = @()

$getCurrentGroupMembership = (Get-ADGroup -Server $domain $groupName | Get-ADGroupMember -Recursive | Select-Object -Unique | Sort | Select-Object SamAccountName)

$getCurrentGroupMembership | Export-CSV -Path $outputLocation\$outputFileName -Append -NoTypeInformation

$inputFileNameLastWeek     = Import-Csv -Path "$outputLocation\$importFileNameLastWeek"

$objectCompare     = @{

  ReferenceObject  = ($getCurrentGroupMembership)
  DifferenceObject = ($inputFileNameLastWeek)

}

$deltaNames = (Compare-Object @objectCompare).InputObject

if($deltaNames -ne $null){

  $deltaNames | Export-Csv -Path $outputLocation\$deltaFileName -Append -NoTypeInformation
  $numberNewUsers = "New Users: " + ($deltaNames).Count

  $report += $deltaNames
  $report += $numberNewUsers
}

if($deltaNames -eq $null){

  Exit

}


$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"
$body  = $report | ConvertTo-Html -Head $style

$dateEmail        = Get-Date -Format "MM-dd-yyyy"
$emailFrom        = "EmailFrom@company.com"
$emailTo          = "Someone@company.com"
$subject          = "Weekly Group Check | $dateEmail" 
$email            = New-object System.Net.Mail.MailMessage 
$email.to.Add($emailTo)
$email.From       = New-Object system.net.Mail.MailAddress $emailFrom
$email.Subject    = $subject
$email.IsBodyHtml = $true
$email.body       = $body
$attachment       = "$outputLocation\$deltaFileName"
$email.Attachments.Add($attachment)

$smtpserver       = "mail.company.com" 
$smtp             = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)


And after you save that file, you will need to create the scheduled task. Make sure this is a machine that will be on and available during the time you want to run the script. I’m using “weeklychanges.ps1” as the example saved file name, running at 10am on Saturdays. Below will need to be ran from an elevated prompt / ISE in order to create the task without getting an “Access denied” error.

$taskName    = 'WeeklyGroupCheck'
$time        = "10am"
$user        = "domain\user"
$credentials = Get-Credential -Credential $user
$password    = $credentials.GetNetworkCredential().Password
$scriptPath  = "c:\scripts\weeklycheck.ps1"
$trigger     = New-ScheduledTaskTrigger -Weekly -At $time -DaysOfWeek Saturday
$action      = New-ScheduledTaskAction -Execute "PowerShell.exe" -Argument "-ExecutionPolicy Bypass -noprofile -file $scriptPath" 

Register-ScheduledTask -TaskName $taskName -Trigger $trigger -User $user -Password $password -Action $action -RunLevel Highest -Force

How Many Users Are In There? : Getting Group Membership Counts

So you want to know how many people are in the groups. As luck would have it, you can get that. There was an interesting thing that I encountered with Get-ADGroupMember when trying to return a count. If there were 0 members, it returned correctly. If there were 2 or more, it returned correctly. If there was 1 member, it returned nothing. It wasn’t null as I checked that. It just returned nothing. Found the answer on this site as to why it was doing it:Why it returned nothing. TLDR: From Martin9700, PowerShell, when only 1 object is returned it is returned AS that object. Count is property of an array (and you can have an array of pretty much any variable/object type)

So with that in mind, I went the route below to do a Measure-Object, then do the count. That returned the results I expected. I also wanted to only select the unique users in the group just in case there were nested groups that a user might have been in more than one of.

# Script to get user group counts. This requires the AD Powershell module, access rights to AD, a central location of Citrix groups, a naming convention, and used in Powershell ISE 5.1.
$domain          = "domain"
$searchBase      = "OU=CitrixGroups,OU=Groups,DC=somecompany,DC=com"
$getCtxGroups    = (Get-ADGroup -server $domain -SearchBase $searchBase -Filter {SamAccountName -like "CitrixGroupNamePattern*"} | Select-Object SamAccountName)
$totalItems      = ($getCtxGroups).Count
$date            = Get-Date -Format MMddyyyy
$report          = @()
$currentItem     = 0
$percentComplete = 0


ForEach($ctxGroup in $getCtxGroups){
  Write-Progress -Activity "Processing user count on ($ctxGroup).SamAccountName" -Status "$PercentComplete% Complete:" -PercentComplete $PercentComplete
  $userCount      = (Get-ADGroup -Server $domain $ctxGroup.SamAccountName | Get-ADGroupMember -Recursive | Select-Object -Unique | Measure-Object).Count
  $line           = "" | Select GroupName, UserCount
  $line.GroupName = $ctxGroup.SamAccountName
  
  if($userCount -ne 0){
   
    $line.UserCount = $userCount
    
  }
 
  if($userCount -eq 0){
    $line.UserCount = "Empty"
  }
    
  $currentItem++
  $percentComplete = [int](($currentItem / $totalItems) * 100)
  
  $report += $line
}


$report | Export-Csv c:\scripts\logs\$date-ADUserGroup-Counts.csv -Append -NoTypeInformation

Moving Control Plane To Cloud: Migrating Citrix Daily User Report

Third in the series of moving the control plane to Citrix Cloud…. So you had your daily user report kicking out everyday (Surely you created one from this other post: https://xenapplepie.com/2022/04/12/if-you-could-get-those-user-counts-today-that-would-be-great/). It was working its happy way through life. Then you just moved parts it talked to into the cloud. I have created this updated report script to allow for it to pull from Citrix Cloud. This requires that you have already setup your API access with the secureclient.csv, that you added the CustomerID to your secureclient.csv, and you have installed the Citrix Cloud SDK. If you don’t have those, you are gonna have a bad day. I left the comment for the #Get Licensing Info so you can see what all other fields you can get if needed from there. If you are using VS Code, when you run that section, you can create a new variable and assign it as “$content.” and it will show the other available pieces of information you can assign such as “deviceLicenseUsage.”

**Update: Removed line with Get-XDAuthentication as it is doing a double authentication. Changed SDK commands to use $headers.Authorization to pass same bearer token**

Example Of Autocomplete From VS Code For Licensing
Sample Output From Script Email
# Citrix Daily Report with updates for using Citrix Cloud. This was done in Powershell ISE 5.1 with Citrix Cloud SDK installed.

asnp Citrix*

$Today = Get-Date
if(($Today.DayOfWeek) -eq 'Monday')
{$when = $Today.AddDays(-3)}
else{$when = $Today.AddDays(-1)}

$creds          = import-csv "c:\scripts\logs\secureclient.csv"
$CLIENT_ID      = $creds.ID
$CLIENT_SECRET  = $creds.Secret
$CUSTOMER_ID    = $creds.CustomerID
$tokenUrl       = 'https://api-us.cloud.com/cctrustoauth2/root/tokens/clients'

$response       = Invoke-WebRequest $tokenUrl -Method POST -Body @{
  grant_type    = "client_credentials"
  client_id     = $CLIENT_ID
  client_secret = $CLIENT_SECRET
}

$token = $response.Content | ConvertFrom-Json

$headers              = @{
  Accept              = "application/json"
  Authorization       = "CwsAuth Bearer=$($token.access_token)"
  'Citrix-CustomerId' = $CUSTOMER_ID
 }

# Get Licensing Info
$response            = Invoke-WebRequest "https://api-us.cloud.com/licensing/license/enterprise/cloud/cvad/ud/current" -Method Get -Headers $headers
$content             = $response.Content | ConvertFrom-Json
$response.Content | ConvertFrom-Json | ConvertTo-Json -Depth 10
$licensingTotalCount = $content.totalAvailableLicenseCount
$licensingUsageCount = $content.totalUsageCount
$licensingRemaining  = $content.remainingLicenseCount

$connections = Get-BrokerConnectionLog -BearerToken $headers.Authorization  -Filter {BrokeringTime -gt $when} -MaxRecordCount 100000 | Select-Object BrokeringUserName

$CitrixVDIConnected     = (Get-BrokerSession -BearerToken $headers.Authorization  -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object SessionState -eq "Active").count
$CitrixVDIDisconnected  = (Get-BrokerSession -BearerToken $headers.Authorization  -MaxRecordCount 100000 | Where-Object SessionSupport -eq "SingleSession" | Where-Object SessionState -eq "Disconnected").count

$ctxUsers = [PSCustomObject] @{

  UniqueCitrixUsers      = ($connections.BrokeringUserName | Select-Object -Unique).count
  CurrentSessions        = (Get-BrokerSession -BearerToken $headers.Authorization -MaxRecordCount 100000 | Select-Object BrokeringUserName).count
  CitrixVDISessions      = $CitrixVDIConnected + $CitrixVDIDisconnected
  CitrixLicensesUsed     = $licensingUsageCount
  CitrixTotalLicenses    = $licensingTotalCount
  CtxLicenseFreePercent  = ((($licensingUsageCount) / $licensingTotalCount ) * 100).ToString("#.##")

}

# HTML Formatting
$style = "<style>BODY{font-family: Arial; font-size: 10pt;}"
$style = $style + "TABLE{border: 1px solid black; border-collapse: collapse;}"
$style = $style + "TH{border: 1px solid black; background: #dddddd; padding: 5px; }"
$style = $style + "TD{border: 1px solid black; padding: 5px; }"
$style = $style + "</style>"

$body = $ctxUsers | ConvertTo-Html -Head $style 

$date             = Get-Date -Format "MM-dd-yyyy"
$emailFrom        = "someemail@company.com"
$emailto          = "someemail@company.com"
$emailtwo         = "someemail@company.com"
$emailCC          = "someemail@company.com"
$subject          = "Daily Citrix User Report | $date" 
$email            = New-object System.Net.Mail.MailMessage 
$email.to.Add($emailto)
$email.to.Add($emailtwo)
$email.CC.Add($emailCC)
$Email.From       = New-Object system.net.Mail.MailAddress $emailFrom
$email.Subject    = $subject
$email.IsBodyHtml = $true
$email.body       = $body
$smtpserver       = "smtp.company.com" 
$smtp             = new-object Net.Mail.SmtpClient($smtpServer)
$smtp.Send($email)

Links to other articles in the series:

Part 1 Of Cloud Migration Series: Part 1

Part 2 Of Cloud Migration Series: Part 2

Part 4 Of Cloud Migration Series: Part 4

Powered by WordPress & Theme by Anders Norén