I thought it would be fun to completely nerd out and have a page dedicated to some of my favorite scripts/code that I’ve written over the years. My code is by no means the most efficient or the best, but I enjoy writing it and try to improve on it any chance I get.
Multi-Tenant-License-Check.ps1
This came about as a need to verify across all our Partner Center customers how many licenses were assigned to accounts they should not have been, in an effort to save clients monthly costs. The script runs as an Azure Runbook and iterates through each client in Partner Center, gathering all license information. It then parses the information and compiles a full HTML report on exactly what issues it found. It emails the results to a given address and also saves a copy of the CSV to an Azure Storage blob.
<# Multi-Tenant-Licenses-Check.ps1 .SYNOPSIS Checks all users for each client and make sure their licensing is appropriate. .DESCRIPTION Gets all license data for all users in each client environment. Compares/contrasts the licenses of each user and determines if any changes can be made to delete unnecessary/etc. Does comparissons based on notes from SUP-471096 which are as follows: The following changes can be made without review - - SPE_E5 doesn't also need - EMS - EMSPREMIUM - ENTERPRISEPACK corpcoe - Only needs EXCHANGESTANDARD corpreporting - Only needs EXCHANGESTANDARD admin, breakglass, corp, corpadmin should not have any licenses Use https://docs.microsoft.com/en-us/azure/active-directory/enterprise-users/licensing-service-plan-reference as reference The following changes must be reviewed before going ahead - - ENTERPRISEPACK should include EMS - ENTERPRISEPACK should not include EMSPREMIUM - Disabled users shouldn't have paid licenses (free are fine). All human users should preferably have SPE_E5 - unless specific reasons otherwise noted. .NOTES Created By: Stephen Gemme Created On: 01/17/22 Modification info/version control in Git. #> # Used for when I run it manually to test. Param ( [Switch] $manual, [Switch] $remediate ) # Setup our global arrays for reporting purposes. # Used to store all license info for the CSV output. [System.Collections.ArrayList]$Global:allUserLicenses = @() # Used to check if anyone who is enabled is missing licenses. [System.Collections.ArrayList]$Global:allUserNoLicenses = @() # Anyone who has additional, unnecessary licenses on top of E5 [System.Collections.ArrayList]$Global:E5erroneous = @() # Any Admin, corp, Breakglass, etc accounts with licenses [System.Collections.ArrayList]$Global:admin = @() # Any corpCOE with more than just Exchange Online Plan 1 [System.Collections.ArrayList]$Global:corpCOE = @() # Any corpReporting with more than just [System.Collections.ArrayList]$Global:corpReporting = @() # Any O365 E3 missing Enterprise Mobility + Security E3 [System.Collections.ArrayList]$Global:o365E3 = @() # Used to compose our email. $Global:EmailBody = "" function getLicenseInfo(){ # Gather all our partners. $partners = (Get-MsolPartnerContract).TenantID.GUID # Iterate through each partner. foreach ($partner in $partners) { Write-Host -foregroundColor CYAN "`n-----------------------------`n$($partner)" # Gather all users within the given partner. $users = Get-MsolUser -TenantID $partner -all # Iterate through each user and gather their license info. foreach ($user in $users) { $licenses = $null $entry = $null # I split up the array so it's easier, yes, easier, to search things later (with a regex). $licenses = (($user.licenses | Select-Object -expandProperty AccountSkuid) | foreach-object {$_.Split(":")[1]}) -join ',' if ($null -ne $licenses -and $licenses -ne ""){ $entry = [PSCustomObject]@{ UserPrincipalName = $user.UserPrincipalName Licenses = $licenses BlockCredential = $user.BlockCredential } $Global:allUserLicenses.add($entry) | Out-Null } else { $entry = [PSCustomObject]@{ UserPrincipalName = $user.UserPrincipalName Licenses = "" BlockCredential = $user.BlockCredential } $Global:allUserNoLicenses.add($entry) | Out-Null } } } # Now that we're done gathering our data, let's evaluate it. checkLicenseInfo } function checkLicenseInfo(){ # Start our report so we have everything in order. if ($Global:uk){ $name = "UK" } elseif ($Global:us){ $name = "US" } # Add our intro to the email. $Global:EmailBody += "The following data shows all incorrect Microsoft License assignments based on requirements in SUP-471096 for <b>$($name)</b> clients.</br>" $Global:EmailBody += "You can get all details about the script that runs this and remediation steps to take by <a href='URL'>going to this Confluence page.</a><br></br>" # Get our E5 users. $E5 = $Global:allUserLicenses | Where-Object {$_.Licenses -like '*SPE_E5*'} # Get our E3 users. $allO365E3 = $Global:allUserLicenses | Where-Object {$_.Licenses -like '*ENTERPRISEPACK*' -and $_.Licenses -notlike '*SPE_E5*'} # Get any specific accounts with licenses that shouldn't. We do the -notMatch because we don't care if they have free ones. $adminLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'admin@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'FLOW_FREE|TEAMS_EXPLORATORY'} $corpLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'corp@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'FLOW_FREE|TEAMS_EXPLORATORY'} $corpadminLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'corp*admin@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'FLOW_FREE|TEAMS_EXPLORATORY'} $breakglassLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'break*glass@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'FLOW_FREE|TEAMS_EXPLORATORY'} # Make sure if a corpcoe or reporting account is licensed, it's the correct one. $coeLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'corp*coe@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'EXCHANGESTANDARD|FLOW_FREE|TEAMS_EXPLORATORY' } $reportingLic = $Global:allUserLicenses | Where-Object {$_.UserPrincipalName -like 'corp*reporting@*' -and $_.Licenses -like '*' -and $_.Licenses -notMatch 'EXCHANGESTANDARD|FLOW_FREE|TEAMS_EXPLORATORY'} # Get any users who are disabled but still have licenses, or any users who don't have licenses but are still enabled. # We're ignoring AMP because we don't manage their user licensing. # As per SUP-500875 we're also ignoring a large array of users. $ignoreThese = @('user0@fqdn0','user1@fqdn0','user0@fqdn1') $disabledLic = $Global:allUserLicenses | Where-Object {$_.BlockCredential -eq $true -and $_.UserPrincipalName -notlike '*fqdn0*' -and $ignoreThese -notcontains $_.UserPrincipalName} $enabledNoLic = $Global:allUserNoLicenses | Where-Object {$_.BlockCredential -eq $false -and $_.UserPrincipalName -notlike '*fqdn0*' -and $ignoreThese -notcontains $_.UserPrincipalName} # Output the details for us - I know this is clunky/ugly, but it looks nice when output. # NOTE: I use ($obj | measure).count because it's far more reliable. Write-Host -foregroundColor CYAN "`nQuick Summary:`n---------------------`n" Write-Host "Total Licensed Users: " -noNewLine Write-Host -ForegroundColor CYAN "$(($Global:allUserLicenses | measure).count - 1)" # Remove header line in count. Write-host "Total E5 Users: " -NoNewline Write-Host -foregroundColor CYAN "$(($e5 | Measure).count)" # Add the totals to the report. $Global:EmailBody += "<b>Total Licensed Users:</b> $(($Global:allUserLicenses | measure).count)<br>`n" $Global:EmailBody += "<b>Total E5 Users: </b> $(($e5 | Measure).count)<br>`n" $Global:EmailBody += "<b>Total E3 Users: </b> $(($allO365E3 | measure).count)<br>`n" # I know this is a little overdone, but I wanted to be thorough. We're only reporting on license issues if they exist. if (($adminLic | Measure).count -gt 0 -or ($corpLic | Measure).count -gt 0 -or ($corpadminLic | Measure).count -gt 0 -or ($breakGlassLic | Measure).count -gt 0 -or ($coeLic | Measure).count -gt 0 -or ($reportingLic | Measure).count -gt 0 -or ($disabledLic | Measure).count -gt 0 -or ($enabledNoLic | Measure).count -gt 0){ # We found at least 1 license issue. We need to check which one and report it. $Global:EmailBody += "</hr><h3>Licensing issues:</h3>" # An extremely rare example of me not formatting properly. if (($adminLic | Measure).count -gt 0){ $Global:EmailBody += "<b>admin with Licenses: </b> $(($adminLic | Measure).count)<br>`n" } if (($corpLic | Measure).count -gt 0){ $Global:EmailBody += "<b>corp with Licenses: </b> $(($corpLic | Measure).count)<br>`n" } if (($corpadminLic | Measure).count -gt 0){ $Global:EmailBody += "<b>corpadmin with Licenses: </b> $(($corpadminLic | Measure).count)<br>`n" } if (($breakglassLic | Measure).count -gt 0){ $Global:EmailBody += "<b>breakglass with Licenses: </b> $(($breakglassLic | Measure).count)<br>`n" } if (($coeLic | Measure).count -gt 0){ $Global:EmailBody += "<b>corpcoe with more than ExchangeStandard: </b> $(($coeLic | Measure).count)<br>`n" } if (($reportingLic | Measure).count -gt 0){ $Global:EmailBody += "<b>corpreporting with more than ExchangeStandard: </b> $(($reportingLic | Measure).count)<br></br>`n" } if (($disabledLic | Measure).count -gt 0){ $Global:EmailBody += "<b>Disabled users with Licenses: </b> $(($disabledLic | Measure).count)<br>`n" } if (($enabledNoLic | Measure).count -gt 0){ $Global:EmailBody += "<b>Enabled users without Licenses: </b> $(($enabledNoLic | Measure).count)<br>`n" } # Now that we've got the basic report done, let's build the table. # Add the table + header. $Global:EmailBody += "<table width='100%' border='1'><tbody><tr bgcolor=#CCCCCC><td align='center'>UserPrincipalName</td><td align='center'>License Issue Found</td><td align='center'>Current Licenses</td></tr>" # If any of the below have licenses, list exactly which account has them in the table and reasoning behind it. Write-Host "admin with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($adminLic | Measure).count)" Write-Host "corp with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($corpLic | Measure).count)" Write-Host "corpadmin with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($corpadminLic | Measure).count)" Write-Host "breakglass with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($breakglassLic | Measure).count)" Write-Host "breakglass with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($breakglassLic | Measure).count)" Write-Host "Disabled with Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($disabledLic | Measure).count)" Write-Host "Enabled without Lic: " -NoNewline Write-Host -foregroundColor CYAN "$(($enabledNoLic | Measure).count)" # I know I could save space by making these into a function and passing variables, but I tried that and it screwed up the table somehow. if (($adminLic | Measure).count -gt 0){ foreach ($user in $adminLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report. $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>admin account shouldn't have licenses.</td><td>$($user.Licenses)</td></tr>`n" } } if (($corpLic | Measure).count -gt 0){ foreach ($user in $corpLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report. $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>corp account shouldn't have licenses.</td><td>$($user.Licenses)</td></tr>`n" } } if (($corpadminLic | Measure).count -gt 0){ foreach ($user in $corpadminLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report. $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>corpadmin account shouldn't have licenses.</td><td>$($user.Licenses)</td></tr>`n" } } if (($breakglassLic | Measure).count -gt 0){ foreach ($user in $breakglassLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report. $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>breakglass account shouldn't have licenses.</td><td>$($user.Licenses)</td></tr>`n" } } if (($coeLic | Measure).count -gt 0){ foreach ($user in $coeLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report: $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>corpcoe has more than Exchange Online (Plan 1)</td><td>$($user.Licenses)</td></tr>`n" } } if (($reportingLic | Measure).count -gt 0){ foreach ($user in $reportingLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # Add them to the report: $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>corpreporting has more than Exchange Online (Plan 1)</td><td>$($user.Licenses)</td></tr>`n" } } if (($disabledLic | Measure).count -gt 0){ foreach ($user in $disabledLic){ Write-Host "`t$($user.UserPrincipalName) `t`t- $($user.Licenses)" # We only care if the disabled user is costing the client money, ignore free licenses. if ($user.Licenses -ne "TEAMS_EXPLORATORY" -and $user.Licenses -ne "FLOW_FREE"){ # Add them to the report. $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>Disabled accounts shouldn't have licenses.</td><td>$($user.Licenses)</td></tr>`n" } } } # Find the users who have E5 but also one of reseller-account:EMS, reseller-account:EMSPREMIUM, reseller-account:ENTERPRISEPACK Write-Host "`nChecking if any E5 users have additional licenses they should not.`n---------------------" foreach ($user in $E5){ # First check if the user has more than 1 license and isn't part of AMP, who we don't manage. if ($user.Licenses.split(',').length -gt 1 -and $user.UserPrincipalName -notmatch 'fqdn0|fqdn1'){ # Now that we know they have more than 1, let's narrow it down to the ones that don't need to exist with E5. # Remember, kids, -match is a regex comparitor - don't forget to escape special characters! if ($user.Licenses -match 'EMSPREMIUM|EMS|ENTERPRISEPACK'){ Write-Host "$($user.UserPrincipalName) has conflicting: " -noNewLine Write-Host -foregroundColor MAGENTA "$($user.Licenses)" # Add them to the report: $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>User has E5 License but also unnecessary additional.</td><td>$($user.Licenses)</td></tr>`n" } } } # Find the users who have Office 365 E3 but DON'T also have Enterprise Mobility + Security E3 Write-Host "`nChecking if any O365 EE users are missing Enterprise Mobility + Security E3.`n---------------------" foreach ($user in $allO365E3){ # First check if the user has more than 1 license. if ($user.Licenses.split(',').length -gt 1 -and $user.UserPrincipalName -notmatch 'fqdn0|fqdn1'){ # If they have more than 1, see if Enterprise Mobility + Security E3 exists in there. if ($user.Licenses -notlike '*EMS*'){ # They're missing Enterprise Mobility + Security E3, report it. Write-Host "$($user.UserPrincipalName) has O365 E3 but is missing " -NoNewline Write-Host -ForegroundColor MAGENTA "EMS" # Add them to the report: $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>User has O365 E3 but is missing Enterprise Mobility + Security E3.</td><td>$($user.Licenses)</td></tr>`n" } } else { # No additional licenses assigned, but make sure they're not AMP since we don't manage this. if ($user.UserPrincipalName -notmatch 'fqdn0|fqdn1'){ # They're missing Enterprise Mobility + Security E3, report it. Write-Host "$($user.UserPrincipalName) has O365 E3 but is missing " -NoNewline Write-Host -ForegroundColor MAGENTA "EMS" # Add them to the report: $Global:EmailBody += "`t<tr><td>$($user.UserPrincipalName)</td><td>User has O365 E3 but is missing Enterprise Mobility + Security E3.</td><td>$($user.Licenses)</td></tr>`n" } } } # Make sure we close our table. $Global:EmailBody += "</table><br />" } else { $Global:EmailBody += "</hr><h3>No Licensing Issues Found! This is informationl and may be closed.</h3>" } # If we're in manual mode, don't email/save to azure. Save locally. if ($Global:manual){ $Global:EmailBody | Out-File "$($PSScriptRoot)\License-Report-$(Get-Date -format MM-dd-yyyy).html" } else { endTheReport } } function remediateLicenses($thingsToFix){ # Function is exactly what you think it is - it attempts to perform any/all remediations that are pre-approved. # Let's iterate through each one (if we have any), and make appropriate changes. if (($thingsToFix | measure).count -gt 0) { foreach ($user in $thingsToFix){ Write-Host "Remediating user: " -NoNewline Write-Host -ForegroundColor CYAN $user.Userprincipalname } } } function sendTheReport() { $date = Get-Date -format MM-dd-yyyy if ($Global:us){ $pre = "US" } elseif($Global:uk){ $pre = "UK" } $credObject = Get-AutomationPSCredential -Name "smtp-relay-account" # Setup our message parameters. $messageParameters = @{ To = '' From = "" Subject = "$($pre) User License Report $($date)" Body = $Global:EmailBody Credential = $credObject SmtpServer = "smtp.office365.com" Port = 587 } # Try to send the email. try { Send-MailMessage @messageParameters -BodyAsHtml -UseSsl Write-Host "Sent mail" } catch { Write-Host -ForegroundColor RED "Error Composing Mail" } # After sending the email, save the output to our file. saveToStorage } function saveToStorage(){ # Gather our storage accounts. $StorageAccountReportsName = Get-AutomationVariable -Name "storage-account-reports-name" $StorageAccountReportsContainer = Get-AutomationVariable -Name "storage-account-reports-container" $StorageAccountReportsSas = Get-AutomationVariable -Name "storage-account-reports-sas" # Setup our file. $date = Get-Date -Format MM-dd-yy $output = "User-Licese-Report-$($date).html" # Save our HTML to the file: $Global:EmailBody | Out-File -FilePath $output -Append # Send the file to storage under the user-license-report container. $StorageAccountContext = New-AzStorageContext -StorageAccountName $StorageAccountReportsName -SasToken $StorageAccountReportsSas $tmp = Set-AzStorageBlobContent -File $output -Container $StorageAccountReportsContainer -Context $StorageAccountContext -Force } function SignInToMsolService(){ Write-Host "Signing in to Msol Service" try { $credentials = Get-AutomationPSCredential -Name 'client-script-automation' Connect-MsolService -credential $credentials # Now that we're signed in, start getting the info. getLicenseInfo } catch { Write-Error "Failed to sign in to Partner Center" } } ############################# # # # MAIN PROGRAM # # # ############################# # For Runbooks, we can exactly specify what we want. $Global:us = $true # I'm not sure why I can't declare this global in the param - it doesn't want to work. $Global:manual = $manual $Global:remediate = $remediate # Used in testing to get the local files without scanning. if ($Global:us -or $Global:uk) { if ($Global:manual){ getLicenseInfo } else { SignInToMsolService } } else { Write-Warning "Must Specify either -UK or -US. Exiting." exit 1 }
Get-Logon-Stats.ps1
A complex auditing script primarily made for terminal servers. It checks a provided list of Active Directory computer objects and parses their system logs to determine the total number of logins each machine received. Arguments (flags) can be used to count all logins or only unique-users. The results can be output to a GridView or mapped into a visual Chart and saved as an image. Results can optionally be emailed.
<# Get-Logon-Stats.ps1 .SYNOPSIS This app gathers logon/logoff and timestamp information about specified computers. .DESCRIPTION Using Get-ADComputer as its base, this script will gather machines by SearchBase and Filter. It will then iterate through each machine's log files and find all logon/logoff events within a specified amount of days using the "previousDaysToSearch" variable. Parsing the logs one at a time, the script then records any logon event and searches for its subsequent logoff event. If found, the logon and logoff event times are recorded and the difference calculated. With each iteration, the script neatly displays all pertinent information about the machine it just searched. .NOTES Created By: Stephen Gemme Created On: 03/16/2020 Modified By: Stephen Gemme Modified On: 03/17/2020 Modifications: Created Email capability and separated gathered information into custom hash arrays per computer. Also added a switch to control if ALL information is sent or just the totals for each machine. Created a switch and capability to generate a chart for totals that is saved as a JPEG. Modified By: Stephen Gemme Modified On: 03/18/2020 Modifications: Added support for CPU usage gathering by means of Galen's CPU script running on each WinTS host. #> Param ( [Parameter(Mandatory = $false)] [Switch] $sendEmail, [Parameter(Mandatory = $false)] [Switch] $fullReport, [Parameter(Mandatory = $false)] [Switch] $createLogChart, [Parameter(Mandatory = $false)] [Switch] $createCPUChart, [Parameter(Mandatory = $false)] [Switch] $testMode ) # Set email information. For multiple Recipients, separate the values with a comma $HeaderFrom = 'outbound.com' $SMTPServer = 'smtp.com' $Subject = "Windows Terminal Server Usage Report" $Recipients = 'me@myplace' $global:EmailBody = "" $Global:TotalLogons = 0 $Global:TotalLogoffs = 0 $Global:TotalTimeSpan = 0 [System.Collections.ArrayList]$Global:allIndividualResults = @() [System.Collections.ArrayList]$Global:allComputerResults = @() # How far back to look through the logs. $previousDaysToSearch = 1 # This is used so that I can test this script on a single entity without having to change a bunch of prod-level stuff. if ($testMode){ Write-Warning "Running in Test Mode" $searchBase = "DC=admin,DC=myplace,DC=com" $filter = 'Name -like "win-ts-p-w07"' } else { # Make sure this is updated if we're using this script anywhere but for terminal servers. $searchBase = "OU=Session Hosts,OU=WinTS,OU=Terminal Servers,DC=admin,DC=myplace,DC=com" $filter = "*" } function gatherLogData(){ foreach ($computer in (Get-ADComputer -searchBase $searchBase -filter $filter | Select-Object -ExpandProperty DNSHostName).toLower()){ # Reset our variables for each computer we search: $Global:TotalLogons = 0 $Global:TotalTimeSpan = 0 [System.Collections.ArrayList]$individualComputerResults = @() Write-Host "`nGetting Logon/Loggoff stats for " -NoNewline Write-Host -ForegroundColor CYAN $computer -NoNewLine Write-Host " for the past " -NoNewline Write-Host -ForegroundColor CYAN $previousDaysToSearch -NoNewline Write-Host " day(s).`n" # Gather all our logon/off events for the specified computer(s) and day(s). $LogonEvents = Get-EventLog System -Source Microsoft-Windows-WinLogon -After (Get-Date).addDays(-$previousDaysToSearch) -ComputerName $computer | Sort-Object TimeWritten $totalEvents = $LogonEvents.Length # Itterate through the whole array, keeping track of our position. for ($ii = 0; $ii -lt $totalEvents; $ii++){ # Get our pertinent information. $guid = $LogonEvents[$ii].ReplacementStrings[1] $logonTimeStamp = $LogonEvents[$ii].TimeWritten $userName = (New-Object System.Security.Principal.SecurityIdentifier $LogonEvents[$ii].ReplacementStrings[1]).Translate([System.Security.Principal.NTAccount]) # If we find a logon event, record the data and search for its corrresponding logoff. if ($LogonEvents[$ii].InstanceId -eq 7001){ $Global:TotalLogons++ # With our info, search the rest of the logs for the logoff event, if there is one. for ($jj = $ii + 1; $jj -lt $totalEvents; $jj++){ # Only worry about logoff events. if ($LogonEvents[$jj].InstanceId -eq 7002){ # If the GUIDs match, we know this person logged off. if ($guid -eq $LogonEvents[$jj].ReplacementStrings[1]){ $logoffTimeStamp = $LogonEvents[$jj].TimeWritten # Write everything to the host and get out of this loop. $timeSpan = $logoffTimeStamp - $logonTimeStamp $Global:TotalTimeSpan = $Global:TotalTimeSpan + $timeSpan.TotalMilliseconds #Write-Host -ForegroundColor CYAN $userName -NoNewline #Write-Host " was signed in for " -NoNewline #Write-Host -ForegroundColor CYAN $timeSpan $user = [PSCustomObject]@{ Username = $userName ObjectSID = $guid LogonTime = $logonTimeStamp LogoffTime = $logoffTimeStamp SessionLength = $timeSpan } $individualComputerResults.Add($user) | Out-Null break } } } } } # This is here so we don't divide by 0 (Only Chuck Norris can do that.) if ($Global:TotalLogons -ne 0){ $avgTime = [timespan]::fromseconds($Global:TotalTimeSpan / $Global:TotalLogons / 1000 / 60) } else { $avgTime = 0 } $results = [PSCustomObject]@{ ComputerName = $computer TotalLogons = $Global:TotalLogons AvgSessionLength = $avgTime } # Add the cumulative results to the other list. $Global:allComputerResults.Add($results) | Out-Null $allIndividualResults = [PSCustomObject]@{ ComputerName = $computer Results = $individualComputerResults } $Global:allIndividualResults.Add($allIndividualResults) | Out-Null } # Now that we have our results, put them into a chart! generateLogChart } function generateLogChart(){ # Import our necessary Chart assemblies. Add-Type -AssemblyName "System.Windows.Forms" Add-Type -AssemblyName "System.Windows.Forms.DataVisualization" # Instantiate our chart and data objects. $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart # Set our Chart Properties $Chart.Width = 1360 $Chart.Height = 530 $Chart.Left = 10 $Chart.Top = 10 $Chart.BackColor = [System.Drawing.Color]::LightGray $Chart.BorderColor = 'Black' $Chart.BorderDashStyle = 'Solid' # Set the Chart Title $ChartTitle = New-Object System.Windows.Forms.DataVisualization.Charting.Title $ChartTitle.Text = " Total Logons Per Host Over $previousDaysToSearch Day(s)" $Font = New-Object System.Drawing.Font @('Microsoft Sans Serif','12', [System.Drawing.FontStyle]::Bold) $ChartTitle.Font =$Font $Chart.Titles.Add($ChartTitle) # chart area $chartarea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $chartarea.Name = "ChartArea" $chartarea.AxisY.Title = "Logins" $chartarea.AxisX.Title = "Time of Day" $chartarea.AxisX.Minimum = 0 $chartarea.AxisX.Interval = 2 $chart.ChartAreas.Add($chartarea) # legend $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $legend.name = "Legend" $Chart.Legends.Add($legend) # Begin setting data. $x = @("Midnight", "1:00 AM", "2:00 AM", "3:00 AM", "4:00 AM", "5:00 AM", "6:00 AM", "7:00 AM", "8:00 AM", "9:00 AM", "10:00 AM", "11:00 AM", "Noon", "1:00 PM", "2:00 PM", "3:00 PM", "4:00 PM", "5:00 PM", "6:00 PM", "7:00 PM", "8:00 PM", "9:00 PM", "10:00 PM", "11:00 PM") $y = @(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) # For every computer that we searched foreach ($computer in $Global:allIndividualResults){ Write-Host "Trying to get results for $computer" try { $y = @(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) $Chart.Series.Add($computer.ComputerName) $Chart.Series[$computer.ComputerName].ChartType = "Line" $Chart.Series[$computer.ComputerName].IsVisibleInLegend = $true # For every logon entry for that computer foreach ($entry in $computer.Results){ # Check if we're talking AM or PM $dateStr = [String]$entry.LogonTime $time = [int]($dateStr).split(" ")[1].split(":")[0] $y[$time - 1]++ } # Both X and Y values need to be in arrays because PowerShell automatically parses them and places them on the chart. $Chart.Series[$computer.ComputerName].Points.DataBindXY($x,$y) } catch { Write-Host "Error getting Log Data for $computer" } # Save the chart to a designated spot. $date = (Get-Date).ToString('yyyy-MM-dd') $savePath = $PSScriptRoot + "\Win-TS-Logon-Stats-$date.png" # Save the Chart $Chart.SaveImage("$savePath","png") } <# Setup and display the Chart $AnchorAll = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right -bor [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left $Form = New-Object Windows.Forms.Form $Form.Width = 1400 $Form.Height = 600 $Form.controls.add($Chart) $Chart.Anchor = $AnchorAll $Form.Add_Shown({$Form.Activate()}) [void]$Form.ShowDialog()#> } function generateCPUChart(){ if ($previousDaysToSearch -gt 7){ Write-Warning "The getCPUusage option can only be used if 'previousDaysToSearch' is 7 or fewer." } else { # Import our necessary Chart assemblies. Add-Type -AssemblyName "System.Windows.Forms" Add-Type -AssemblyName "System.Windows.Forms.DataVisualization" # Instantiate our chart and data objects. $Chart = New-object System.Windows.Forms.DataVisualization.Charting.Chart # Set our Chart Properties $Chart.Width = 1660 $Chart.Height = 730 $Chart.Left = 10 $Chart.Top = 10 $Chart.BackColor = [System.Drawing.Color]::LightGray $Chart.BorderColor = 'Black' $Chart.BorderDashStyle = 'Solid' # Set the Chart Title $ChartTitle = New-Object System.Windows.Forms.DataVisualization.Charting.Title $ChartTitle.Text = "CPU Usage Over $previousDaysToSearch Day(s)" $Font = New-Object System.Drawing.Font @('Microsoft Sans Serif','12', [System.Drawing.FontStyle]::Bold) $ChartTitle.Font =$Font $Chart.Titles.Add($ChartTitle) # chart area $chartarea = New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea $chartarea.Name = "ChartArea" $chartarea.AxisY.LabelStyle.Format = "##.###############"; $chartarea.AxisY.Title = "Usage Percentage" $chartarea.AxisX.Title = "Time of Day" $chartarea.AxisX.Minimum = 0 $chartarea.AxisX.Interval = $previousDaysToSearch * 15 $chart.ChartAreas.Add($chartarea) # legend $legend = New-Object system.Windows.Forms.DataVisualization.Charting.Legend $legend.name = "Legend" $Chart.Legends.Add($legend) foreach ($computer in (Get-ADComputer -searchBase $searchBase -filter $filter | Select-Object -ExpandProperty DNSHostName).toLower()){ try { $x = @() $y = @() $Chart.Series.Add($computer) $Chart.Series[$computer].ChartType = "Line" $Chart.Series[$computer].IsVisibleInLegend = $true # This file is updated every 5 minutes and contains the last week's worth of data. $CPUdata = Get-Content "\\$computer\c$\Windows\temp\cpucount" [array]::reverse($CPUdata) # We only want to get data that's within our time frame. # Number of days we're requesting times hours in a day times number of 5 minute segments in an hour. $linesToTraverse = $previousDaysToSearch * 24 * 12 # Iterate through all the lines, averaging out each 5 minute segment to an hour. for ($ii = $linesToTraverse; $ii -ge 0; $ii--){ $dataDate = (Get-Date).AddMinutes(-($ii * 5)) $x += "$dataDate" $y += [decimal]$CPUData[$ii] } # Both X and Y values need to be in arrays because PowerShell automatically parses them and places them on the chart. $Chart.Series[$computer].Points.DataBindXY($x, $y) } catch { Write-Host "Error getting CPU Data for $computer" } } # Save the Chart to a desginated spot. $date = (Get-Date).ToString('yyyy-MM-dd') $savePath = $PSScriptRoot + "\Win-TS-CPU-Stats-$date.png" # Save the Chart $Chart.SaveImage("$savePath","png") <# Setup and display the Chart $AnchorAll = [System.Windows.Forms.AnchorStyles]::Bottom -bor [System.Windows.Forms.AnchorStyles]::Right -bor [System.Windows.Forms.AnchorStyles]::Top -bor [System.Windows.Forms.AnchorStyles]::Left $Form = New-Object Windows.Forms.Form $Form.Width = 1700 $Form.Height = 800 $Form.controls.add($Chart) $Chart.Anchor = $AnchorAll $Form.Add_Shown({$Form.Activate()}) [void]$Form.ShowDialog()#> } } function composeMailMessage(){ Write-Host "Composing Message..." -noNewLine $date = (Get-Date).ToString('yyyy-MM-dd') $logonsavePath = $PSScriptRoot + "\Win-TS-Logon-Stats-$date.png" $cpusavePath = $PSScriptRoot + "\Win-TS-CPU-Stats-$date.png" # Only send the message with attachments if we can find them. if ((Test-Path $logonsavePath) -and (Test-Path $cpusavePath)){ $messageParameters = @{ Subject = $Subject Body = $global:EmailBody From = $HeaderFrom To = $Recipients Attachment = $logonsavePath, $cpusavePath SmtpServer = $SMTPServer Priority = "Low" } } elseif((Test-Path $logonsavePath) -and (-NOT (Test-Path $cpusavePath))){ $messageParameters = @{ Subject = $Subject Body = $global:EmailBody From = $HeaderFrom To = $Recipients Attachment = $logonsavePath SmtpServer = $SMTPServer Priority = "Low" } } elseif((Test-Path $cpusavePath) -and (-NOT (Test-Path $logonsavePath))){ $messageParameters = @{ Subject = $Subject Body = $global:EmailBody From = $HeaderFrom To = $Recipients Attachment = $cpusavePath SmtpServer = $SMTPServer Priority = "Low" } } else { $messageParameters = @{ Subject = $Subject Body = $global:EmailBody From = $HeaderFrom To = $Recipients SmtpServer = $SMTPServer Priority = "Low" } } try { Send-MailMessage @messageParameters -BodyAsHtml Write-Host -ForegroundColor GREEN "Done" } catch { Write-Host -ForegroundColor RED "Error Composing Mail" } } #===== Start of Script =====# # Add our entry to the email first regardless of if we're sending one. $global:EmailBody += "The following data represents the previous <b><u>$previousDaysToSearch</u></b> day(s) of usage.<br />" # Based on what parameters were specified, do our thang! if ($createLogChart){ # This automatically calls generateLogChart once it's done. gatherLogData } if ($createCPUChart){ generateCPUChart } # When we're done, and if the sendEmail flag was sent, start composing. if ($sendEmail){ # Table for all computer results. # Name Computer Name Total Logons Total Logoffs Avg Session Length (dd:HH:mm.ss) $Global:EmailBody += "<table width='100%' border='1'><tbody><tr bgcolor=#CCCCCC><td align='center'>Computer Name</td><td align='center'>Total Logons</td><td align='center'>Avg. Session Length (dd:hh:mm.ss)</td></tr>" foreach ($entry in $Global:allComputerResults){ $Global:EmailBody += " <tr><td>$($entry.ComputerName)</td><td>$($entry.TotalLogons)</td><td>$($entry.AvgSessionLength)</td></tr>" } # Make sure we close our table. $Global:EmailBody += "</table><br />" # If the user wants to know everything, let 'em have it! if ($fullReport){ foreach ($computer in $Global:allIndividualResults){ # Add our header to denote the computer we're talking about below. $Global:EmailBody += "<table width='100%' border='1'><tbody><tr bgcolor=#CCCCCC><td colspan=11><strong>Host:</strong> $computer.ComputerName - <strong>Total Sessions:</strong> $entry.TotalLogons</td></tr>" foreach ($entry in $computer.Results){ # Name Username ObjectSID Logon Time Logoff Time Session Length $Global:EmailBody += "<tr bgcolor=#CCCCCC><td align='center'>Username</td><td align='center'>ObjectSID</td><td align='center'>Logon Time</td><td align='center'>Logoff Time</td><td align='center'>Session Length</td></tr>" $Global:EmailBody += " <tr><td>$($entry.Username)</td><td>$($entry.ObjectSID)</td><td>$($entry.LogonTime)</td><td>$($entry.LogoffTime)</td><td>$($entry.SessionLength)</td></tr>" } # Make sure we close our table and add space for the next one. $Global:EmailBody += "</table><br />" } } # Write the email and send it. composeMailMessage }
Acct.ps1
A simple script to look up and perform simple Active Directory account statuses like expired passwords. This was born out of a necessity of, and in likeness of, an “ldapsearch” tool for Windows.
<# Acct.ps1 Created By: Stephen Gemme Created On: 9/01/2015 This is a fairly large script that contains a few subsets/tools. Just like the Linux ./acct script, this one accepts arguments such as: test - Test Login Credentials reset - Reset Password (please do this on Linux for Single Sign On Purposes) status - Get All pertinent Active Directory information. setID - Set the user's "Description" to your choosing - we like it being the ID# There are quite a few built-in checks and balances in the code, and we've tested it fairly thoroughly, so it's pretty darn stable, if I say so myself. #> # Check that we have an admin Shell open before trying to do anything. <#If (-NOT ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(` [Security.Principal.WindowsBuiltInRole] "Administrator")) { Write-Warning "You do not have Administrator rights to run this script!`nPlease re-run this script as an Administrator!" exit } else { # Default is to continue without prompting to verify info. User must specify -i to prompt. $continue = $true } #> $continue = $true # Check that we have the correct Modules imported: if (-not (Get-Module ActiveDirectory)) { try{ "No Active Directory Module Found, importing..." Import-Module ActiveDirectory -Force } catch { write-host -foregroundColor "RED" -backgroundColor "BLACK" "`nError importing Active Directory module.`nMake sure you have RSAT installed. `nExiting.`n" exit } } # Reset user account function reset ($username) { try { # Make sure we have a valid user and can access their account in AD. $ADuser = Get-ADUser $username if($ADuser) { # User wants to verify the info before continuing. if ($changeExpire) { [int]$gradYear = read-host "Expected Graduation Year" try { Set-ADAccountExpiration $username -DateTime "09/01/$gradYear" $expireDate = Get-ADUser -identity $username -Properties AccountExpirationDate | Select-Object -ExpandProperty AccountExpirationDate write-host -foregroundColor "GREEN" "Account expiration successfully changed to $expireDate." exit } catch { write-host -foregroundcolor "RED" -backgroundcolor "BLACK" "`nError Setting New Expiration Date" exit } } "Checking to make sure account is enabled and not locked...`n" status($username) # Take in the user's new password. while (-Not $success) { $password = Read-Host 'Password' -AsSecureString $verify = Read-Host 'Verify' -AsSecureString # Using a pointer, convert the secure password to a string that allows comparison. $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($password) $unsecurepw = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr) [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr) # Convert our verified password to be compared to the first. $Ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToCoTaskMemUnicode($verify) $unsecurevf = [System.Runtime.InteropServices.Marshal]::PtrToStringUni($Ptr) [System.Runtime.InteropServices.Marshal]::ZeroFreeCoTaskMemUnicode($Ptr) if ($unsecurepw -eq $unsecurevf) { $success = $true } else { # P.I.C.N.I.C. write-host -foregroundcolor "RED" -backgroundcolor "black" "`nPasswords did not match." } }# End WHILE !success. # If the passwords match, go about performing the change. if ($success) { try { # Attempt to change the password. Set-adaccountpassword $username -reset -newpassword $password -server "ad-dc.myplace.com" write-host -foregroundcolor "GREEN" "Password Successfully Changed!`n" # If our -t argument was given, we need to make the appropriate change. if ($temppassword) { Set-aduser $username -changepasswordatlogon $true write-host -foregroundcolo "YELLOW" "User Must Reset Password On Next Login." } else { # By default, we don't want them to have to reset their passwords on next login. Set-aduser $username -changepasswordatlogon $false }# End IF temp break } catch { # Unknown error, possibly due to a disabled/expired account. # Could also be caused by a password which doesn't meet complexity requirements. write-host -foregroundColor "RED" -backgroundcolor "black" "`nError when attempting to change passwords." exit } }# End IF success }# End IF ADUser } # End TRY catch { # Error when attempting to locate user in AD. write-host -foregroundcolor "RED" -backgroundcolor "black" "`nUser Not Found." exit } finally { }# End FINALLY }# End FUNCTION resetPassword function status($username) { # Get the AD info, output it. Get-ADUser -identity $username -Properties AccountExpires, LockedOut, HomeDirectory, whenCreated, passwordExpired, description, pwdLastSet, whencreated, whenChanged # Check to make sure the user isn't disabled. $enabled = (get-aduser $username -properties enabled).enabled if (-Not $enabled){ write-warning "User Account is disabled!`n" $unlockFirst = (read-host "Enable Before Continuing? [Y/N]").ToLower() if ($unlockFirst -eq "y" -or $unlockFirst -eq "yes") { try { Enable-ADAccount $username write-host -foregroundcolor "GREEN" "`nSuccessfully enabled $username!" } catch { write-host -foregroundcolor "RED" -backgroundcolor "BLACK" "`nError enabling account $username." } } } else { write-host -foregroundcolor "GREEN" "Account is enabled." } # Check to make sure the user isn't locked out. $lockedOut = (get-aduser $username -properties lockedout).lockedout if ($lockedOut -eq "True"){ write-warning "User Account is locked!`n" $unlockFirst = (read-host "Unlock Before Continuing? [Y/N]").ToLower() if ($unlockFirst -eq "y" -or $unlockFirst -eq "yes") { try { Unlock-ADAccount $username write-host -foregroundcolor "GREEN" "`nSuccessfully unlocked $username!" } catch { write-host -foregroundcolor "RED" -backgroundcolor "BLACK" "`nError unlocking account $username." } } } else { write-host -foregroundcolor "GREEN" "Account is unlocked." } # Check to make sure the user isn't expired. $todaysDate = (GET-DATE) $expireDate = Get-ADUser -identity $username -Properties AccountExpirationDate | Select-Object -ExpandProperty AccountExpirationDate if ($null -ne $expiredate -and $expiredate -ne "") { if ((get-date $expireDate) -lt (get-date $todaysDate)) { # Expired write-warning "User Account has Expired!`n" # Specifying a graduation year will set the expiration date to September 1st of that year. $changeExpiration = (read-host "Clear Expiration Date? [Y/N]").ToLower() if ($changeExpiration -eq "y" -or $changeExpiration -eq "yes") { try { Clear-ADAccountExpiration $username write-host -foregroundColor "GREEN" "Account expiration successfully cleared." } catch { write-host -foregroundcolor "RED" -backgroundcolor "BLACK" "`nError Clearing Expiration Date" } } } elseif ((get-date $todaysDate).AddDays(30) -gt (get-date $expireDate)) { # Expiring in 30 days or less. write-warning "Account Expiring Soon!`n" # Specifying a graduation year will set the expiration date to September 1st of that year. $changeExpiration = (read-host "Change Expiration Date? [Y/N]").ToLower() if ($changeExpiration -eq "y" -or $changeExpiration -eq "yes") { [int]$gradYear = read-host "Expected Graduation Year [yyyy]" try { Set-ADAccountExpiration $username -DateTime "09/01/$gradYear" $expireDate = Get-ADUser -identity $username -Properties AccountExpirationDate | Select-Object -ExpandProperty AccountExpirationDate write-host -foregroundColor "GREEN" "Account expiration successfully changed to $expireDate." } catch { write-host -foregroundcolor "RED" -backgroundcolor "BLACK" "`nError Setting New Expiration Date" } } } else { # Not Expired write-host -foregroundColor "GREEN" "Account has not expired." } } else { # Never Expires write-host -foregroundColor "GREEN" "Account cannot expire." } # Check to make sure the password isn't expired. $passExpire = Get-ADUser -identity $username -Properties PasswordExpired | Select-Object -ExpandProperty PasswordExpired if ($passExpire -eq "True") { # Expired write-warning "Password has Expired! Recommend Reset.`n" return } else { # Not Expired write-host -foregroundColor "GREEN" "Password has not expired.`n" } <# Check to see if there is a description for the account. $description = Get-ADUser -identity $username -Properties Description | Select-Object -ExpandProperty Description if ($description -eq $null -or $description -eq "") { write-warning "No ID set for account, recommend setting one with 'acct setID'.`n" }#> return }# End FUNCTION status function credtest { $cred = Get-Credential #Read credentials $username = $cred.username $password = $cred.GetNetworkCredential().password # Check to make sure the user isn't locked out. $lockedOut = (get-aduser $username -properties lockedout).lockedout # Check to make sure the user isn't disabled. $enabled = (get-aduser $username -properties enabled).enabled # Get current domain using logged-on user's credentials $CurrentDomain = "LDAP://" + ([ADSI]"").distinguishedName # For the domain, omit the "\" since it places one there automatically. Not sure why, this is a bug. $username="admin$username" $domain = New-Object System.DirectoryServices.DirectoryEntry($CurrentDomain,$UserName,$Password) if ($null -eq $domain.name -and $enabled -and $lockedOut -eq "True") { write-host -foregroundcolor "RED" -backgroundcolor "black" "`nFailed to authenticate. :(`n" write-host -foregroundcolor "RED" -backgroundcolor "black" "Bad Password`n" exit #terminate the script. } elseif ($null -eq $domain.name -and -NOT $enabled) { write-host -foregroundcolor "RED" -backgroundcolor "black" "Failed to authenticate.`n" write-host -foregroundcolor "RED" -backgroundcolor "black" "Account not enabled.`n" } elseif ($null -eq $domain.name -and $lockedOut -eq "True") { write-host -foregroundcolor "RED" -backgroundcolor "black" "Failed to authenticate.`n" write-host -foregroundcolor "RED" -backgroundcolor "black" "Account locked.`n" } else { write-host -foregroundcolor "GREEN" "`nSuccessfully authenticated!`n" } }# End FUNCTION credtest function setID ($username) { $currentID = Get-ADuser $username -Properties description | Select-Object -ExpandProperty description if ($null -ne $currentID -and $currentID -ne ""){ Write-warning "`User already has ID set to: $currentID" $overWrite = (Read-Host "`nOverwrite? [Y/n]").toLower() } else { $overWrite = "y" } if ($overWrite -eq "y") { $description = Read-Host "New User ID" try { Set-ADUser $username -description $description Write-Host -foregroundColor Green "`nSuccessfully Changed $username's ID to: " -noNewLine Write-Host "$description`n" exit } catch { write-host -foregroundcolor "RED" -backgroundcolor "black" "Error setting User ID.`n" exit } } else { Write-Host "Skipping ID Change." } }# End FUNCTION setID function displayHelp { Write-Host -foregroundColor Cyan "Acct.ps1`n" "USAGE: acct [arg0] [arg1]`n" "reset [args] | Reset specified user's password.`n" "setID [args] | Set the user's ID (description).`n" "status [args] | Check account status including expirations.`n" "test | Simulate a Windows Logon with results.`n" break } #End FUNCTION displayHelp # Check to see if any arguments were given. if ($args -ne $null){ # Reference so we can access arguments directly. $ii = 0 # Parse through all arguments one at a time. Save $ii as a reference. foreach ($element in $args) { $element = $element.ToLower() if ($element -eq "reset") { # reset arg passed, attempt to grab username from next arg. if ($null -ne $args[$ii + 1] -and $args[$ii + 1] -ne " ") { $username = $args[$ii + 1] } else { $username = Read-Host "Username" } reset ($username) } elseif ($element -eq "status") { # status arg passed, attempt to grab username from next arg. if ($null -ne $args[$ii + 1] -and $args[$ii + 1] -ne " ") { $username = $args[$ii + 1] } else { $username = Read-Host "Username" } status ($username) } elseif ($element -eq "setid"){ # setID arg passed, attempt to grab username from next arg. if ($null -ne $args[$ii + 1] -and $args[$ii + 1] -ne " ") { $username = $args[$ii + 1] } else { $username = Read-Host "Username" } setID ($username) } elseif ($element -eq "test") { credtest } } } # No arguments specified. # Displaying Help page by default. else { displayHelp }
DynamicListManagement.ps1
A script created out of necessity to more adequately manage our Distribution Groups (used for emailing lists) in an academic environment where users move around often and lists need to be maintained. This script is part of a large project to begin utilizing Dynamic AzureAD groups to more accurately sync users to lists.
The script’s primary function is to gather a list of members in the Dynamic AzureAD group and 2 like-named Manual AzureAD groups, Opt-in and Opt-out, respectively. The script uses a combination of the Dynamic members and manual “opt” group members to determine an array of users who should be in a given list. The script then compares the current list with the combined results and adds/removes users as needed.
<# .SYNOPSIS DynamicListManagement.ps1 does the automatic syncing and creation of Dynamic emailing lists. .DESCRIPTION If we want to create a new Dynamic DL we can, provided we give the proper args. Otherwise, we will sync current ones. A "mailing list" will comprise of: The DL (where the mail is sent to) The Dynamic Group containing all the users fitting a particular description The Opt-in group (if needed) The Opt-out group (if needed) So the only important aspect of the opt group creation is making sure they match the naming scheme for later searching The logic will be to act on a list of descriptions, get the list, check existence, gather the lists of dyn and opt in, subtract opt out then sync the DL Dynamic Rule Example: (user.extensionAttribute6 -match ".*acsabuncu.*") and (user.accountEnabled -eq true) #> Param ( # Reports but doesn't actually make changes. [Switch] $testMode, # Used to setup brand new lists. [Switch] $createNew, [Switch] $addOptInList, [Switch] $addOptOutList, [Switch] $convertDL, # Specify based on how we're running this. [Switch] $autoSync, [Switch] $manualSync, [Switch] $syncOptsWithDL, [Switch] $dontSyncOpts, # Used to run more quickly without needing prompts. [String] $dlName, [String] $DYNfilter, [Switch] $noADCheck, [String] $ADfilter, # May not end up using these [Switch] $removeOptInList, [Switch] $removeOptOutList ) # Set path for log files: $logPath = "D:\Logs\DYNManagement\Standing" # Get date for logging and file naming: $date = Get-Date $datestamp = $date.ToString("yyyyMMdd-HHmm") # We're not cleaning these up yet, but this is here when we need it. #Get-ChildItem $logPath -Recurse -Force -ea 0 | Where-Object {$_.LastWriteTime -lt (Get-Date).AddDays(-30)} | #ForEach-Object { # $_ | del -Force #} Start-Transcript -Append -Path "$($logPath)\$($datestamp)_DYNMgmt.log" -Force if ($testMode){ Write-Warning "Test Mode Enabled, no actual changes will be made." } function test-Existing { [CmdletBinding()] Param( [Parameter(Position=0, Mandatory=$true)] $listName, [Parameter(Position=0, Mandatory=$true)] $listType ) # Depending on what kind of list we're checking, we need to do things a little differently. if ($listType -eq "AzureAD"){ $list = (Get-AzureADMSGroup -SearchString $listname -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq $listname}) } elseif ($listType -eq "DL"){ $list = get-DistributionGroup $listname } elseif ($listType -eq "DynamicDL"){ $list = Get-DynamicDistributionGroup -Identity $listName -ErrorAction SilentlyContinue } if ($list){ return $true } else { return $false } } #Create a dynamic list #needs to take a simple description function New-DynamicGroup { [CmdletBinding()] param ( $Name, $Rule ) # Create a new Dynamic Group that will add members based on a filter. Write-Host "Creating new Dynamic Group: DYN-$($Name)" if (-NOT $testMode){ # If the list already exists, let the user know and give them a choice. if (test-Existing -listname "Dyn-$($name)" -listType "AzureAD"){ Write-Warning "Dyn-$($name) already exists!" $overwrite = (Read-Host "Overwrite existing? [Y|n]").toLower() if ($overwrite -eq "y"){ Write-Host -foregroundColor RED "Deleting and Re-creating Dyn-$($name)" $dyn = Get-AzureADMSGroup -SearchString "Dyn-$($name)" | Where-Object {$_.DisplayName -eq "Dyn-$($name)"} $dyn | Set-AzureADMSGroup "Dynamic group created from PS" -MailEnabled $False -MailNickName "Dyn-$name" -SecurityEnabled $True -GroupTypes "DynamicMembership" -MembershipRule $Rule -MembershipRuleProcessingState "On" } else { Write-Host -foregroundColor CYAN "Dyn-$($name) has not been altered." } } else { New-AzureADMSGroup -DisplayName "Dyn-$($name)" -Description "Dynamic group created from PS" -MailEnabled $False -MailNickName "Dyn-$($name)" -SecurityEnabled $True -GroupTypes "DynamicMembership" -MembershipRule $Rule -MembershipRuleProcessingState "On" } } } function New-OptInList { [CmdletBinding()] param ( $Name ) # Create a new AzureAD Group that people can be manually added/removed. Write-Host "Creating new AzureADGroup: OptIn-$($Name)" if (-NOT $testMode){ # If the list already exists, let the user know and give them a choice. if (test-Existing -listname "OptIn-$($name)" -listType "AzureAD"){ Write-Warning "OptIn-$($name) already exists!" $overwrite = (Read-Host "Overwrite existing? [Y|n]").toLower() if ($overwrite -eq "y"){ Write-Host -foregroundColor RED "Deleting and Re-creating OptIn-$($name)" $dyn = Get-AzureADMSGroup -SearchString "OptIn-$($name)" | Where-Object {$_.DisplayName -eq "OPTIn-$($name)"} $dyn | Set-AzureADMSGroup -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet" } else { Write-Host -foregroundColor CYAN "OptIn-$($name) has not been altered." } } else { New-AzureADMSGroup -DisplayName "OptIn-$($name)" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet" } } } function New-OptOutList { [CmdletBinding()] param ( $Name ) # Create a new AzureAD Group that people can be manually added/removed. Write-Host "Creating new AzureADGroup: OptOut-$($Name)" if (-NOT $testMode){ # If the list already exists, let the user know and give them a choice. if (test-Existing -listname "OptOut-$($name)" -listType "AzureAD"){ Write-Warning "OptOut-$($name) already exists!" $overwrite = (Read-Host "Overwrite existing? [Y|n]").toLower() if ($overwrite -eq "y"){ Write-Host -foregroundColor RED "Deleting and Re-creating OptOut-$($name)" $dyn = Get-AzureADMSGroup -SearchString "OptOut-$($name)" | Where-Object {$_.DisplayName -eq "OPTOut-$($name)"} $dyn | Set-AzureADMSGroup -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet" } else { Write-Host -foregroundColor CYAN "OptOut-$($name) has not been altered." } } else { New-AzureADMSGroup -DisplayName "OptOut-$($name)" -MailEnabled $false -SecurityEnabled $true -MailNickName "NotSet" } } } function New-MailEnabledSyncGroup { [CmdletBinding()] param ( $Name ) # Create the Distribution List that emails will be sent to. Write-Host "Creating new Distribution List: DL-$($Name)" if (-NOT $testMode){ # If the list already exists, let the user know and give them a choice. if (test-Existing -listname "DL-$($name)" -listType "DL"){ Write-Warning "DL-$($name) already exists!" $overwrite = (Read-Host "Overwrite existing? [Y|n]").toLower() if ($overwrite -eq "y"){ Write-Host -foregroundColor RED "Deleting and Re-creating DL-$($name)" Remove-DistributionGroup -identity "DL-$($name)" -confirm:$false New-DistributionGroup -Name "DL-$($Name)" -Alias "DL-$($Name)" -Type Distribution -PrimarySmtpAddress "Dl-$($name)@myplace.com" } else { Write-Host -foregroundColor CYAN "DL-$($name) has not been altered." } } else { New-DistributionGroup -Name "DL-$($Name)" -Alias "DL-$($Name)" -Type Distribution -PrimarySmtpAddress "Dl-$($name)@myplace.com" } } } function Sync-OldWithNew{ [CmdletBinding()] param ( $name, $rule, $filter, $optIn, $optOut ) <# .SYNOPSIS Okay this is a bit much but need to put it here. .DESCRIPTION We need to: - Get all users who are currently in the existing DL. - Get the proper Dynamic Filter setup for the new list. - Compare/Contrast current DL entries with new filter. - Auto add users to OptIn/Out to reflect current DL - That way, the new Dynamic list exists but it has already taken into account the old one and anyone who had Rich manually opt in/out. #> # Get who's currently in the DL $currentDLMembers = Get-DistributionGroupMember -identity "DL-$($name)" | Select-Object -expandProperty PrimarySMTPAddress Write-Host "`nMembers in Current DL: $($currentDLMembers.count)" # Get who should be in the DL based on the Get-ADUser filter. $allADMembers = (Get-ADuser -filter "$filter") Write-Host "Members Found With Filter: $($allADMembers.count)" # Diff the results so we see who is missing/added for each. $diffResults = Compare-Object $allADMembers.UserPrincipalName $currentDLMembers -PassThru Write-Host "Total Differences: $($diffResults.count)" $AddMembers = $diffResults | Where-Object { $_.SideIndicator -eq '=>' } Write-Host "Members to be Opted-IN: $($AddMembers.count)" $RemoveMembers = $diffResults | Where-Object { $_.SideIndicator -eq '<=' } Write-Host "Members to be Opted-OUT: $($RemoveMembers.count)" # Now that we've gathered everything, let's start setting things up! if (-NOT $testMode){ # I know we do this elsewhere and I should save the code, but I really just wanted everything in here since it was special. if ($addOptInList){ New-OptInList $Name $optInCreated = $true } else { # Make sure before continuing. Write-Host -foregroundColor Black -BackgroundColor Gray "`nOpt-In list not specified." $youSure = (Read-Host "Create Opt-In List? [Y|n]").toLower() if ($youSure -eq "y"){ New-OptInList $Name $optInCreated = $true } } if ($addOptOutList){ New-OptOutList $Name $optOutCreated = $true } else { # Make sure before continuing. Write-Host -foregroundColor Black -BackgroundColor Gray "`nOpt-Out list not specified." $youSure = (Read-Host "Create Opt-Out List? [Y|n]").toLower() if ($youSure -eq "y"){ New-OptOutList $Name $optOutCreated = $true } } # If we created either, add who we need to add. if ($optInCreated -and (-NOT $dontSyncOpts)){ $DynList = Get-AzureADMSGroup -SearchString "OptIn-$($name)" | Where-Object {$_.DisplayName -eq "OPTIn-$($name)"} ForEach ($Addition in $AddMembers) { # We have to do this because the compare-object stripped the property away earlier. $userToAdd = (Get-AzureADUser -SearchString "$($addition)") Write-Host -ForegroundColor GREEN "Adding Member to Opt-In: $($addition)" Add-AzureADGroupMember -ObjectID $DynList.ID -RefObjectId $userToAdd.ObjectID } } if ($optOutCreated -and (-NOT $dontSyncOpts)){ $DynList = Get-AzureADMSGroup -SearchString "OptOut-$($name)" | Where-Object {$_.DisplayName -eq "OPTOut-$($name)"} ForEach ($Removal in $RemoveMembers) { # We have to do this because the compare-object stripped the property away earlier. $userToRemove = (Get-AzureADUser -SearchString "$($Removal)") Write-Host -foregroundColor RED "Adding Member to Opt-Out: $($removal)" Add-AzureADGroupMember -ObjectId $DynList.ID -RefObjectId $userToRemove.ObjectID } } # Now that the opt-in/out lists are set, we need to make the new DYN. New-DynamicGroup -Name $Name -Rule $Rule # Note: We don't need to create the DL since it already exists, and we don't need to sync it since we just diff'd everything against it. } } function Sync-OPTsWithDL{ [CmdletBinding()] param ( $name ) <# .SYNOPSIS Sync an existing DYN and its OPT IN/OUT with the current DL. .DESCRIPTION We needed this because we ended up making all the DYN and OPT groups ahead of time. That means we then needed to sync those with the existing DLs before moving forward. Otherwise, everyone from the DYN would be added to the DL and we don't want that. We need to: - Get all users who are currently in the existing DL. - Compare/Contrast current DL entries with who's in the DYN. - Auto add users to OptIn/Out to reflect current DL #> # Get who's currently in the DL $currentDLMembers = Get-DistributionGroupMember -identity "DL-$($name)" | Select-Object -expandProperty PrimarySMTPAddress Write-Host "`nMembers in Current DL: $($currentDLMembers.count)" # Get who should be in the DL based on who's in the DYN $dyn = (Get-AzureADMSGroup -SearchString "DYN-$($name)" | Where-Object {$_.DisplayName -eq "DYN-$($name)"}) $dynMembers = (Get-AzureADGroupMember -ObjectId $dyn.ObjectID -all $true) Write-Host "Members in Current DYN: $($dynMembers.count)" if ($dynMembers.count -le 1){ Write-Warning "DYN list has no members, please check rule. No sync will be performed." } else { # Diff the results so we see who is missing/added for each. $diffResults = Compare-Object $dynMembers.UserPrincipalName $currentDLMembers -PassThru Write-Host "Total Differences: $($diffResults.count)" $AddMembers = $diffResults | aWhere-Object { $_.SideIndicator -eq '=>' } Write-Host "Members to be Opted-IN: $($AddMembers.count)" $RemoveMembers = $diffResults | Where-Object { $_.SideIndicator -eq '<=' } Write-Host "Members to be Opted-OUT: $($RemoveMembers.count)" # Get our OPT lists so we can work on them. $inList = Get-AzureADMSGroup -SearchString "OptIn-$($name)" | Where-Object {$_.DisplayName -eq "OPTIn-$($name)"} $outList = Get-AzureADMSGroup -SearchString "OptOut-$($name)" | Where-Object {$_.DisplayName -eq "OPTOut-$($name)"} # Now that we've gathered everything, let's start setting things up! ForEach ($Addition in $AddMembers) { # We have to do this because the compare-object stripped the property away earlier. $userToAdd = (Get-AzureADUser -SearchString "$($addition)") Write-Host -ForegroundColor GREEN "Adding Member to Opt-In: $($addition)" if (-NOT $testMode){ Add-AzureADGroupMember -ObjectID $inList.ID -RefObjectId $userToAdd.ObjectID } } ForEach ($Removal in $RemoveMembers) { # We have to do this because the compare-object stripped the property away earlier. $userToRemove = (Get-AzureADUser -SearchString "$($Removal)") Write-Host -foregroundColor RED "Adding Member to Opt-Out: $($removal)" if (-NOT $testMode){ Add-AzureADGroupMember -ObjectId $outList.ID -RefObjectId $userToRemove.ObjectID } } } } function Sync-Dynlist { [CmdletBinding()] param ( $name ) # Null these out each time just in case $OptIn = $OptOut = $DynGroup = $DynList = $null # Get any lists associated with the list we're searching for. if (test-Existing -listname "OptIn-$($name)" -listType "AzureAD"){ $OptIn = get-AzureADMSGroup -SearchString "OptIn-$($name)" -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq "OPTIn-$($name)"} } if (test-Existing -listname "OptOut-$($name)" -listType "AzureAD"){ $OptOut = get-AzureADMSGroup -SearchString "OptOut-$($name)" -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq "OPTOut-$($name)"} } if (test-Existing -listname "DYN-$($name)" -listType "AzureAD"){ $DynGroup = get-AzureADMSGroup -SearchString "Dyn-$name" -ErrorAction SilentlyContinue | Where-Object {$_.DisplayName -eq "DYN-$($name)"} } if (test-Existing -listname "DL-$($name)" -listType "DL"){ $DynList = get-DistributionGroup -Identity "DL-$($name)" } # Get the members of each group (but only if they exist) if ($null -ne $OptIn){ Write-Host -foregroundColor GREEN "OptIn-$($name) exists. Getting members." $OptInMembers = Get-AzureADGroupMember -ObjectId $OptIn.Id | Select-Object UserPrincipalName } else { $OptIn = "" } if ($null -ne $OptOut){ Write-Host -foregroundColor GREEN "OptIn-$($name) exists. Getting members." $OptOutMembers = Get-AzureADGroupMember -ObjectId $OptOut.Id | Select-Object UserPrincipalName } else { $OptOut = "" } if ($null -ne $DynGroup){ Write-Host -foregroundColor GREEN "DYN-$($name) exists. Getting members." $DynGroupMembers = Get-AzureADGroupMember -ObjectId $DynGroup.Id | Select-Object UserPrincipalName } else { # If this doesn't exist, we MAY have a problem, so let the user know. Write-Warning "No DYN-$($name) exists, make sure this is what you want (it could be, I dunno)." } $regex = '(?i)^(' + (($OptOutMembers | ForEach-Object { [regex]::escape($_) }) -join "|") + ')' $CorrectMembers = ($DynGroupMembers + $OptInMembers) -notmatch $regex $CurrentMembers = Get-DistributionGroupMember -Identity $DynList.DisplayName | Select-Object @{N = 'UserPrincipalName'; E = { $_.primarysmtpaddress } } if ($null -eq $CurrentMembers) { Write-Host -foregroundColor YELLOW "No members currently in list, adding all." ForEach ($member in $CorrectMembers) { if (-NOT $testMode){ #Add-DistributionGroupMember -Identity $DynList.DisplayName -Member $member.UserPrincipalName } } } else { # #reconcile lists $comparisons = Compare-Object $CurrentMembers $CorrectMembers -Property UserPrincipalName $AddMembers = $comparisons | Where-Object { $_.SideIndicator -eq '=>' } | Select-Object userprincipalname $RemoveMembers = $comparisons | Where-Object { $_.SideIndicator -eq '<=' } | Select-Object userprincipalname ForEach ($Removal in $RemoveMembers.userprincipalname) { Write-Host -foregroundColor RED "Removing Member: $removal" if (-NOT $testMode){ #Remove-DistributionGroupMember -Identity $DynList.DisplayName -Member $removal -Confirm:$false } } ForEach ($Addition in $AddMembers.userprincipalname) { Write-Host -ForegroundColor GREEN "Adding Member: $addition" if (-NOT $testMode){ #Add-DistributionGroupMember -Identity $DynList.DisplayName -Member $Addition } } } } function loginToEchange($credentials){ Write-Host "Trying to login to Exchange with credentials..." $ExchangeOnlineSession=$null try { ## Load Exchange Online $ExchangeOnlineSession = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell -Credential $credentials -Authentication Basic -AllowRedirection Import-PSSession $ExchangeOnlineSession Connect-AzureAD -Credential $credentials Write-Host -foregroundColor GREEN "Done." checkWhatWeAreDoing } catch [Exception] { $_.Exception.Message Write-Host -ForegroundColor YELLOW "`n`nUnable to authenticate with saved credentials.`n" Write-Host -foregroundColor YELLOW "This may be due to already having loading the Exchange plugins, please try closing this shell and restarting." exit } } function checkWhatWeAreDoing{ # If we want to make a new list, go for it! iF ($createNew){ Write-Host -foregroundColor YELLOW "`nNOTE: Please enter ONLY the name - naming schemes will be automatically applied." Write-Host "AKA: If you enter 'testDL' the actual DL will be named 'DL-testDL' and Dynamic will be 'DYN-testDL'" $name = Read-Host "Desired List Name:" # We want to define our rule before we continue, so they are 100% certain they know what they're getting into. Write-Host -foregroundColor CYAN "Please Specify the Dynamic List Rule you would like to use." $rule = Read-Host ":" # If we need to make an optin/out, do it. if ($addOptInList){ New-OptInList $Name } else { # Make sure they didn't forget to specify this. Write-Host -foregroundColor Black -BackgroundColor Gray "Opt-In list not specified." $youSure = (Read-Host "Create Opt-In List? [Y|n]").toLower() if ($youSure -eq "y"){ New-OptInList $Name } } if ($addOptOutList){ New-OptOutList $Name } else { # Make sure they didn't forget to specify this. Write-Host -foregroundColor Black -BackgroundColor Gray "Opt-Out list not specified." $youSure = (Read-Host "Create Opt-Out List? [Y|n]").toLower() if ($youSure -eq "y"){ New-OptOutList $Name } } # Do this no matter what. New-MailEnabledSyncGroup $Name New-DynamicGroup -Name $Name -Rule $Rule } elseif ($convertDL){ if ($null -eq $dlName){ # Converting from an old one is a bit more work, but get what we need first. Write-Host -foregroundColor YELLOW "`nNOTE: Please enter ONLY the name - naming schemes will be automatically applied." Write-Host "AKA: If you enter 'testDL' the actual DL will be named 'DL-testDL' and Dynamic will be 'DYN-testDL'`n" $dlName = (Read-Host "Desired List Name") } if ($null -eq $DYNfilter){ # We want to define our rule before we continue, so they are 100% certain they know what they're getting into. Write-Host -foregroundColor CYAN "`nPlease Specify the Dynamic List Rule you would like to use." $DYNfilter= (Read-Host " ") } if ($null -eq $ADFilter -and (-NOT $noADCheck)){ # We want to define our AD filter before we continue, so we can compare/contrast our results. Write-Host -foregroundColor CYAN "`nPlease Specify the Get-ADUser Filter you would like to use." $ADfilter = ((Read-Host "(Enabled Check added Automatically)") + " -and Enabled -eq 'true'") } # Pass all our parameters so we set things up right. if ($noADCheck){ Sync-OldWithNew -name $dlName -rule $DYNfilter -optIn $addOptInList -optOut $addOptOutList } else { Sync-OldWithNew -name $dlName -rule $DYNfilter -filter $ADfilter -optIn $addOptInList -optOut $addOptOutList } } elseif ($manualSync) { # Manually run the sync on a single entity. Write-Host -ForegroundColor YELLOW "`nPut listname only, not DL- or DYN-" $name = Read-Host "Listname to Sync" Sync-Dynlist -name $name } elseif ($autoSync){ # Every list SHOULD have an OptIn list, so search for those to get our targets. $allLists = (Get-AzureADMSGroup -SearchString "OptIn-" -all $true | Select-Object -expandProperty DisplayName) Write-Host "Found $($allLists.count) lists to sync." foreach ($list in $allLists){ # Pull our listname out to make it easier. $listName = $list.split("-", 2)[1] # Send it! Sync-Dynlist -name $listName } } elseif ($syncOptsWithDL){ # Every list SHOULD have an OptIn list, so search for those to get our targets. $allLists = (Get-AzureADMSGroup -SearchString "OptIn-" -all $true | Select-Object -expandProperty DisplayName) Write-Host "Found $($allLists.count) lists to sync." foreach ($list in $allLists){ # Pull our listname out to make it easier. $listName = $list.split("-", 2)[1] # Send it! Sync-OPTsWithDL -name $listName } } } ############################# # # # MAIN PROGRAM # # # ############################# # If we are alreayd logged onto Exchange, no need to try again try{ # If we can get a random, known list, we can get them all. Write-Host "Testing connection to Exchange..." -nonewLine if(Get-DistributionGroup "dl-fuller"){ Write-Host -foregroundColor GREEN "Done." checkWhatWeAreDoing } } catch [Exception] { $_.Exception.Message $credential = $null $credPath = "$PSScriptRoot\$env:UserName.xml" if (Test-path $credPath){ $credential = Import-CliXml -Path $credPath loginToEchange($credential) } else { Write-Warning "Saved Exchange Credentials not Found. Please enter new credentials." $credential = Get-Credential $save = (Read-Host "Would you like to encrypt and save these credentials for future use? [Y|n]").toLower() if ($save -eq "y"){ $credential | Export-CliXml -Path $credPath Write-Host "`nEncrypted credentials saved as: " -nonewLine Write-Host -foregroundColor CYAN $credPath } else { Write-Host -foregroundColor YELLOW "`nCredentials not saved.`n" } loginToEchange($credential) } } Stop-Transcript
TicTacToe.java
I had to put this one here because it amuses me as much as I’m proud to have figured it out. This is taken from one of my Android Apps. These 1500 lines simply play a game of Tic Tac Toe with another user. Who would’ve though Tic Tac Toe was so complex?
The code gets complex primarily because the game is played over the internet by means of Google Firebase. When a user chooses a spot, it sends that choice to an array in the DB. The other phone “reacts” to that choice and determines it is now their turn. Each time the phone sees a choice made, it checks the board to see if there was a winner. The difficulty came with dealing with how to make the phone notice a change in the DB but not the change IT just made, and with creating a timer that would expire at the same time on each of the phones and automatically swap turns even though nothing was selected.
Anyways, enough rambling, here’s the fun.
package IllNeverTell; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.os.CountDownTimer; import androidx.annotation.NonNull; import androidx.constraintlayout.widget.ConstraintLayout; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.google.android.gms.auth.api.signin.GoogleSignIn; import com.google.android.gms.auth.api.signin.GoogleSignInAccount; import com.google.android.gms.games.Games; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.ValueEventListener; import com.plattysoft.leonids.ParticleSystem; import java.util.Locale; import static IllNeverTell.MainActivity.challengesRef; import static IllNeverTell.MainActivity.firebaseAuthUID; import static IllNeverTell.MainActivity.mDatabase; import static IllNeverTell.MainActivity.myDBRef; import static IllNeverTell.MainActivity.roundedLat; import static IllNeverTell.MainActivity.roundedLong; import static IllNeverTell.MainActivity.sendMessageToLogs; import static IllNeverTell.MainActivity.taggedStats; import static IllNeverTell.MainActivity.userName; import static IllNeverTell.MainActivity.usersRef; public class Challenge_TTT extends Activity { private final String TAG = Challenge_TTT.class.getName(); private ConstraintLayout turnLayout; private CountDownTimer countDownTimer; private ImageButton x0y2, x1y2, x2y2, x0y1, x1y1, x2y1, x0y0, x1y0, x2y0; private ImageView player1, player2; private LinearLayout scoreLayout; private TextView title, player1Text, player2Text, countDown, scoreView, statusView; private String challengeIndex; private int myChoice, theirChoice; private int[] score = new int[2]; private Challenge_TTT_Entry entry; private boolean hasGameStarted = false, hasGameEnded = false, amITheChallenger = false; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.challenge_ttt); // Set the score to 0 score[0] = 0; score[1] = 0; // Get our indexes so we know who's entering what choice. 0 Means we're the challenger. if (getIntent().getStringExtra(getString(R.string.challenge_index)).equals("0")) { amITheChallenger = true; challengeIndex = taggedStats.getTaggedByUID(); myChoice = 1; theirChoice = 2; entry = new Challenge_TTT_Entry(0, 0, 0, 0, 0, 0, 0, 0, 0, 1); challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); // Set our watcher for when the person responds to the request. challengesRef.child(challengeIndex).child(getString(R.string.firebase_p2_checkin)).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ // This first check is so that it doesn't run in between rounds when the board is reset. if (dataSnapshot.getValue(boolean.class)){ // We can begin! runOnUiThread(new Runnable() { @Override public void run() { startTurnTimer(); } }); } else { // They declined our request, we win! runOnUiThread(new Runnable() { @Override public void run() { iWinByForfeit(); } }); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); findAllByID(); } else { challengeIndex = firebaseAuthUID; myChoice = 2; theirChoice = 1; hasGameStarted = true; // Check In, so the challenger knows we're here. challengesRef.child(challengeIndex).child(getString(R.string.firebase_p2_checkin)).setValue(true); // Set our listener in case the "host" backs out early. challengesRef.child(challengeIndex).child(getString(R.string.firebase_p1_checkin)).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ try { // The challenger backed out! if (!dataSnapshot.getValue(boolean.class)){ runOnUiThread(new Runnable() { @Override public void run() { iWinByForfeit(); } }); } } catch (Exception e){ Log.e(TAG, e.getLocalizedMessage()); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); // Get the database info each time we load so someone can't try to leave and reset the board. challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()) { try { entry = new Challenge_TTT_Entry( dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_turn)).getValue(int.class)); // Once we have data, continue, otherwise we could null pointer if this doesn't load fast enough. findAllByID(); } catch (NullPointerException e) { Log.e(TAG, e.getMessage()); entry = new Challenge_TTT_Entry(0, 0, 0, 0, 0, 0, 0, 0, 0, 1); } } else { // IMPORTANT If something went wrong and no data exists, exit cleanly and don't change anything. finish(); } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } } private void findAllByID(){ // This is ugly but the only way we can do it. x0y2 = findViewById(R.id.x0y2); x1y2 = findViewById(R.id.x1y2); x2y2 = findViewById(R.id.x2y2); x0y1 = findViewById(R.id.x0y1); x1y1 = findViewById(R.id.x1y1); x2y1 = findViewById(R.id.x2y1); x0y0 = findViewById(R.id.x0y0); x1y0 = findViewById(R.id.x1y0); x2y0 = findViewById(R.id.x2y0); // Get all our other necessities before we start anything else. title = findViewById(R.id.ttt_title); player1 = findViewById(R.id.player1); player2 = findViewById(R.id.player2); player1Text = findViewById(R.id.player1_text); player2Text = findViewById(R.id.player2_text); turnLayout = findViewById(R.id.ttt_turn_layout); countDown = findViewById(R.id.ttt_counter); scoreLayout = findViewById(R.id.ttt_score_layout); scoreView = findViewById(R.id.ttt_score); statusView = findViewById(R.id.ttt_statusText); // Set this now so it shows up as 0 - 0 when the game starts. scoreView.setText(String.format(Locale.getDefault(), "%d - %d", 0, 0)); // Set our watcher for turn changes. challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).child(getString(R.string.firebase_challenge_TTT_turn)).addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ // This first check is so that it doesn't run in between rounds when the board is reset. if (entry.getTurn() != 0){ entry.setTurn(dataSnapshot.getValue(int.class)); // If it's our turn, see what the user picked, if not, wait for them to do so. // We also have to check if it's the first round so we don't act on the data we placed there to begin with. if (entry.getTurn() == myChoice && hasGameStarted){ getLatestInfo(); } } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); if (!hasGameStarted){ runOnUiThread(new Runnable() { @Override public void run() { startGameBeginTimer(); } }); } else { runOnUiThread(new Runnable() { @Override public void run() { startTurnTimer(); } }); } setAllOnClickListeners(); } private void setAllOnClickListeners(){ x0y2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX0y2() != 0){ // Animate the image that's there. if (entry.getX0y2() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x0y2.clearAnimation(); x0y2.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x0y2.clearAnimation(); x0y2.setAnimation(wiggle); } } else { entry.setX0y2(myChoice); if (myChoice == 1){ x0y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x0y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x1y2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX1y2() != 0){ // Animate the image that's there. if (entry.getX1y2() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x1y2.clearAnimation(); x1y2.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x1y2.clearAnimation(); x1y2.setAnimation(wiggle); } } else { entry.setX1y2(myChoice); if (myChoice == 1){ x1y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x1y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x2y2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX2y2() != 0){ // Animate the image that's there. if (entry.getX2y2() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x2y2.clearAnimation(); x2y2.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x2y2.clearAnimation(); x2y2.setAnimation(wiggle); } } else { entry.setX2y2(myChoice); if (myChoice == 1){ x2y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x2y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x0y1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX0y1() != 0){ // Animate the image that's there. if (entry.getX0y1() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x0y1.clearAnimation(); x0y1.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x0y1.clearAnimation(); x0y1.setAnimation(wiggle); } } else { entry.setX0y1(myChoice); if (myChoice == 1){ x0y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x0y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x1y1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX1y1() != 0){ // Animate the image that's there. if (entry.getX1y1() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x1y1.clearAnimation(); x1y1.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x1y1.clearAnimation(); x1y1.setAnimation(wiggle); } } else { entry.setX1y1(myChoice); if (myChoice == 1){ x1y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x1y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x2y1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX2y1() != 0){ // Animate the image that's there. if (entry.getX2y1() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x2y1.clearAnimation(); x2y1.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x2y1.clearAnimation(); x2y1.setAnimation(wiggle); } } else { entry.setX2y1(myChoice); if (myChoice == 1){ x2y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x2y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x0y0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX0y0() != 0){ // Animate the image that's there. if (entry.getX0y0() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x0y0.clearAnimation(); x0y0.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x0y0.clearAnimation(); x0y0.setAnimation(wiggle); } } else { entry.setX0y0(myChoice); if (myChoice == 1){ x0y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x0y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x1y0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX1y0() != 0){ // Animate the image that's there. if (entry.getX1y0() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x1y0.clearAnimation(); x1y0.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x1y0.clearAnimation(); x1y0.setAnimation(wiggle); } } else { entry.setX1y0(myChoice); if (myChoice == 1){ x1y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x1y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); x2y0.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // Only let people pick if it's their turn. if (entry.getTurn() == myChoice){ // Don't try to add anything if something is already there. if (entry.getX2y0() != 0){ // Animate the image that's there. if (entry.getX2y0() == 1){ Animation wobble = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wobble); wobble.reset(); x2y0.clearAnimation(); x2y0.setAnimation(wobble); } else { Animation wiggle = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.wiggle); wiggle.reset(); x2y0.clearAnimation(); x2y0.setAnimation(wiggle); } } else { entry.setX2y0(myChoice); if (myChoice == 1){ x2y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else { x2y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } updateDatabase(); } } else { Toast.makeText(getApplicationContext(), getString(R.string.challenge_ttt_notYourTurn), Toast.LENGTH_SHORT).show(); } } }); } private void updateDatabase(){ try { countDownTimer.cancel(); countDown.setVisibility(View.INVISIBLE); } catch (Exception e ){ Log.i(TAG, "Couldn't stop timer, might have already finished."); } // Swap whose turn it is. if (entry.getTurn() == 1){ entry.setTurn(2); } else { entry.setTurn(1); } challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); updateDisplay(); } private void getLatestInfo(){ // Check the challenger's DB for what they selected and update ours. challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ try { Challenge_TTT_Entry newEntry = new Challenge_TTT_Entry( dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y0)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y1)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x0y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x1y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_x2y2)).getValue(int.class), dataSnapshot.child(getString(R.string.firebase_challenge_TTT_turn)).getValue(int.class)); compareEntries(newEntry); } catch (NullPointerException e){ Log.e(TAG, e.getMessage()); compareEntries(null); } } else { finish(); } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } private void compareEntries(Challenge_TTT_Entry newEntry){ if (newEntry != null){ if (!entry.equals(newEntry)){ // Assume the database is the most up to date and set everything. entry.setTurn(newEntry.getTurn()); entry.setX0y0(newEntry.getX0y0()); entry.setX1y0(newEntry.getX1y0()); entry.setX2y0(newEntry.getX2y0()); entry.setX0y1(newEntry.getX0y1()); entry.setX1y1(newEntry.getX1y1()); entry.setX2y1(newEntry.getX2y1()); entry.setX0y2(newEntry.getX0y2()); entry.setX1y2(newEntry.getX1y2()); entry.setX2y2(newEntry.getX2y2()); } else { // They didn't choose in time, their loss! entry.setTurn(myChoice); challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); } } runOnUiThread(new Runnable() { @Override public void run() { updateDisplay(); } }); } private void updateDisplay(){ if (entry != null){ // Now that we have the latest info, let's update the visuals. if (entry.getTurn() == 1 && myChoice == 1){ player1Text.setText(R.string.challenge_ttt_yourTurn); player2Text.setText(""); player1.clearColorFilter(); player2.setColorFilter(getColor(R.color.transparentBlacker)); } else if(entry.getTurn() == 1 && myChoice == 2){ player1Text.setText(R.string.challenge_ttt_theirTurn); player2Text.setText(""); player1.clearColorFilter(); player2.setColorFilter(getColor(R.color.transparentBlacker)); } else if(entry.getTurn() == 2 && myChoice == 1){ player2Text.setText(R.string.challenge_ttt_theirTurn); player1Text.setText(""); player2.clearColorFilter(); player1.setColorFilter(getColor(R.color.transparentBlacker)); } else if(entry.getTurn() == 2 && myChoice == 2){ player2Text.setText(R.string.challenge_ttt_yourTurn); player1Text.setText(""); player2.clearColorFilter(); player1.setColorFilter(getColor(R.color.transparentBlacker)); } if (entry.getX0y0() == 1){ x0y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX0y0() == 2){ x0y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x0y0.setImageDrawable(null); } if (entry.getX0y1() == 1){ x0y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX0y1() == 2){ x0y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x0y1.setImageDrawable(null); } if (entry.getX0y2() == 1){ x0y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX0y2() == 2){ x0y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x0y2.setImageDrawable(null); } if (entry.getX1y0() == 1){ x1y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX1y0() == 2){ x1y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x1y0.setImageDrawable(null); } if (entry.getX1y1() == 1){ x1y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX1y1() == 2){ x1y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x1y1.setImageDrawable(null); } if (entry.getX1y2() == 1){ x1y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX1y2() == 2){ x1y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x1y2.setImageDrawable(null); } if (entry.getX2y0() == 1){ x2y0.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX2y0() == 2){ x2y0.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x2y0.setImageDrawable(null); } if (entry.getX2y1() == 1){ x2y1.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX2y1() == 2){ x2y1.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x2y1.setImageDrawable(null); } if (entry.getX2y2() == 1){ x2y2.setImageDrawable(getResources().getDrawable(R.drawable.x, getTheme())); } else if (entry.getX2y2() == 2){ x2y2.setImageDrawable(getResources().getDrawable(R.drawable.o, getTheme())); } else { x2y2.setImageDrawable(null); } // If someone won, record it, otherwise start the next player's turn. if (doWeHaveAWinner()){ evaluateScore(); } else { if (!hasGameEnded){ // Now that everything is updated, start the user's timer. runOnUiThread(new Runnable() { @Override public void run() { startTurnTimer(); } }); } } } } private void startGameBeginTimer(){ int timeToChoose = 60 *1000; // 1 Minute // Make sure we can't choose anything right now. disableGUI(); countDown.setVisibility(View.VISIBLE); // Set our title so the challenger knows what's going on. title.setText(R.string.challenge_respond_time); countDownTimer = new CountDownTimer(timeToChoose, 10) { public void onTick(long millisUntilFinished) { String str = String.format(Locale.getDefault(), "%d.%d", millisUntilFinished / 1000, (millisUntilFinished % 1000) / 100); countDown.setText(str); } public void onFinish() { countDown.setVisibility(View.INVISIBLE); //counterText.setVisibility(View.GONE); // No Show - We Win! runOnUiThread(new Runnable() { @Override public void run() { iWinByForfeit(); } }); } }.start(); } private void startTurnTimer(){ if (!hasGameEnded){ final int timeToChoose; // Make sure we can click things now that the round has started. enableGUI(); if (entry.getTurn() == myChoice){ timeToChoose = 10000; // 10 seconds. } else { timeToChoose = 13000; // 11 seconds. Give the DB time to update. } // Update this so we can't get stuck later. hasGameStarted = true; title.setText(R.string.challenge_desc); // Each time we come here, start from scratch since it might still be running. try { countDownTimer.cancel(); // Make it obvious who's counter it is. if (entry.getTurn() == 1){ countDown.setTextColor(getColor(R.color.purple)); } else { countDown.setTextColor(getColor(R.color.accent)); } } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } countDown.setVisibility(View.VISIBLE); countDown.setText(""); countDownTimer = new CountDownTimer(timeToChoose, 10) { public void onTick(long millisUntilFinished) { String str = String.format(Locale.getDefault(), "%d.%d", millisUntilFinished / 1000, (millisUntilFinished % 1000) / 100); if (millisUntilFinished / 1000 < 11) { countDown.setText(str); } } public void onFinish() { countDown.setVisibility(View.INVISIBLE); // Choice wasn't made in time, swap turns. // If the timer was longer, it means they lost their turn, not us. if (timeToChoose == 13000){ entry.setTurn(myChoice); } else { entry.setTurn(theirChoice); } // Update the DB so the phones decide who goes next. challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); runOnUiThread(new Runnable() { @Override public void run() { updateDisplay(); } }); } }.start(); } } private void disableGUI(){ x0y2.setFocusable(false); x0y2.setEnabled(false); x1y2.setFocusable(false); x1y2.setEnabled(false); x2y2.setFocusable(false); x2y2.setEnabled(false); x0y1.setFocusable(false); x0y1.setEnabled(false); x1y1.setFocusable(false); x1y1.setEnabled(false); x2y1.setFocusable(false); x2y1.setEnabled(false); x0y0.setFocusable(false); x0y0.setEnabled(false); x1y0.setFocusable(false); x1y0.setEnabled(false); x2y0.setFocusable(false); x2y0.setEnabled(false); } private void enableGUI(){ x0y2.setFocusable(true); x0y2.setEnabled(true); x1y2.setFocusable(true); x1y2.setEnabled(true); x2y2.setFocusable(true); x2y2.setEnabled(true); x0y1.setFocusable(true); x0y1.setEnabled(true); x1y1.setFocusable(true); x1y1.setEnabled(true); x2y1.setFocusable(true); x2y1.setEnabled(true); x0y0.setFocusable(true); x0y0.setEnabled(true); x1y0.setFocusable(true); x1y0.setEnabled(true); x2y0.setFocusable(true); x2y0.setEnabled(true); } private boolean doWeHaveAWinner(){ boolean isTheWinnerX = false; boolean roundOver = false; boolean tieGame = false; // Here's where we evaluate if the current board has a winner. // Bottom left is X, try all possibilities. if (entry.getX0y0() == 1){ if (entry.getX1y0() == 1 && entry.getX2y0() == 1){ // X is Winner! View view = findViewById(R.id.x0x2y0); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } else if (entry.getX0y1() == 1 && entry.getX0y2() == 1){ // X is Winner! View view = findViewById(R.id.y0y2x0); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } else if (entry.getX1y1() == 1 && entry.getX2y2() == 1){ // X is Winner! View view = findViewById(R.id.x0y0x2y2); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } } // Bottom left is O, try all possibilities. else if (entry.getX0y0() == 2){ if (entry.getX1y0() == 2 && entry.getX2y0() == 2){ // O is Winner! View view = findViewById(R.id.x0x2y0); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } else if (entry.getX0y1() == 2 && entry.getX0y2() == 2){ // O is Winner! View view = findViewById(R.id.y0y2x0); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } else if (entry.getX1y1() == 2 && entry.getX2y2() == 2){ // O is Winner! View view = findViewById(R.id.x0y0x2y2); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } } // Bottom center is X, try all possibilities. if (entry.getX1y0() == 1){ if (entry.getX1y1() == 1 && entry.getX1y2() == 1){ // X is Winner! View view = findViewById(R.id.y0y2x1); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } } // Bottom center is O, try all possibilities. else if (entry.getX1y0() == 2){ if (entry.getX1y1() == 2 && entry.getX1y2() == 2){ // O is Winner! View view = findViewById(R.id.y0y2x1); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } } // Bottom right is X, try all possibilities. if (entry.getX2y0() == 1){ if (entry.getX2y1() == 1 && entry.getX2y2() == 1){ // X is Winner! View view = findViewById(R.id.y0y2x2); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } else if (entry.getX1y1() == 1 && entry.getX0y2() == 1){ // X is Winner! View view = findViewById(R.id.x0y2x2y0); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } } // Bottom right is O, try all possibilities. else if (entry.getX2y0() == 2){ if (entry.getX2y1() == 2 && entry.getX2y2() == 2){ // O is Winner! View view = findViewById(R.id.y0y2x2); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } else if (entry.getX1y1() == 2 && entry.getX0y2() == 2){ // O is Winner! View view = findViewById(R.id.x0y2x2y0); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } } // Middle left is X, try all possibilities. if (entry.getX0y1() == 1){ if (entry.getX1y1() == 1 && entry.getX2y1() == 1){ // X is Winner! View view = findViewById(R.id.x0x2y1); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } } // Middle left is O, try all possibilities. else if (entry.getX0y1() == 2){ if (entry.getX1y1() == 2 && entry.getX2y1() == 2){ // O is Winner! View view = findViewById(R.id.x0x2y1); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } } // Top left is X, try all possibilities. if (entry.getX0y2() == 1){ if (entry.getX1y2() == 1 && entry.getX2y2() == 1){ // X is Winner! View view = findViewById(R.id.x0x2y2); view.setVisibility(View.VISIBLE); isTheWinnerX = true; roundOver = true; } } // Top left is O, try all possibilities. else if (entry.getX0y2() == 2){ if (entry.getX1y2() == 2 && entry.getX2y2() == 2){ // O is Winner! View view = findViewById(R.id.x0x2y2); view.setVisibility(View.VISIBLE); isTheWinnerX = false; roundOver = true; } } // If the board is filled but no one won, start a new one without updating scores. if (!roundOver){ if (entry.getX0y0() != 0 && entry.getX0y1() != 0 && entry.getX0y2() != 0 && entry.getX1y0() != 0 && entry.getX1y1() != 0 && entry.getX1y2() != 0 && entry.getX2y0() != 0 && entry.getX2y1() != 0 && entry.getX2y2() != 0){ roundOver = true; tieGame = true; } } if (roundOver){ // If it wasn't a tie, update the score. if (!tieGame) { if ((isTheWinnerX && myChoice == 1) || (!isTheWinnerX && myChoice == 2)) { // I'm the winner if (amITheChallenger) { score[0] = score[0] + 1; } else { score[1] = score[1] + 1; } } else { // They're the winner. if (amITheChallenger) { score[1] = score[1] + 1; } else { score[0] = score[0] + 1; } } } } return roundOver; } private void evaluateScore(){ if (score[0] != 2 && score[1] != 2){ // Update our score results. runOnUiThread(new Runnable() { @Override public void run() { startNewRound(); } }); } else { // Update our score results. runOnUiThread(new Runnable() { @Override public void run() { announceTheWinner(); } }); } } private void startNewRound(){ if (!hasGameEnded){ // Clear the board first. //entry = new Challenge_TTT_Entry(0, 0, 0, 0, 0, 0, 0, 0, 0, 0); //challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); scoreView.setText(String.format(Locale.getDefault(), "%d - %d", score[0], score[1])); statusView.setTextAppearance(R.style.ChallengeScreenStatusStyle); statusView.setVisibility(View.VISIBLE); scoreLayout.setVisibility(View.INVISIBLE); turnLayout.setVisibility(View.INVISIBLE); // Each time we come here, start from scratch since it might still be running. try { countDownTimer.cancel(); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } // After we've set the score, count down to the next round, clear the board and start over. countDownTimer = new CountDownTimer(5000, 10) { public void onTick(long millisUntilFinished) { String str = String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_next_round), millisUntilFinished / 1000); statusView.setText(str); } public void onFinish() { // When the counter finishes, start the new round by updating the DB statusView.setVisibility(View.GONE); countDown.setVisibility(View.INVISIBLE); scoreLayout.setVisibility(View.VISIBLE); turnLayout.setVisibility(View.VISIBLE); // Setup the new round with no entries. entry = new Challenge_TTT_Entry(0, 0, 0, 0, 0, 0, 0, 0, 0, 1); challengesRef.child(challengeIndex).child(getString(R.string.firebase_challenge_TTT)).setValue(entry); try { findViewById(R.id.x0x2y0).setVisibility(View.INVISIBLE); findViewById(R.id.x0x2y1).setVisibility(View.INVISIBLE); findViewById(R.id.x0x2y2).setVisibility(View.INVISIBLE); findViewById(R.id.y0y2x0).setVisibility(View.INVISIBLE); findViewById(R.id.y0y2x1).setVisibility(View.INVISIBLE); findViewById(R.id.y0y2x2).setVisibility(View.INVISIBLE); findViewById(R.id.x0y0x2y2).setVisibility(View.INVISIBLE); findViewById(R.id.x0y2x2y0).setVisibility(View.INVISIBLE); } catch (Exception e){ Log.e(TAG, "Could not clear board."); } runOnUiThread(new Runnable() { @Override public void run() { updateDisplay(); } }); } }.start(); } } private void announceTheWinner(){ hasGameEnded = true; try { countDownTimer.cancel(); countDown.setVisibility(View.INVISIBLE); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } scoreLayout.setVisibility(View.GONE); if ((score[0] == 2 && amITheChallenger) || (score[1] == 2 && !amITheChallenger)){ // I Win runOnUiThread(new Runnable() { @Override public void run() { iWin(); } }); } else { // I Lose runOnUiThread(new Runnable() { @Override public void run() { iLose(); } }); } } private void iWin(){ statusView.setTextAppearance(R.style.ChallengeScreenWinnerStyle); statusView.setText(R.string.challenge_rps_youWin); new ParticleSystem(this, 200, R.drawable.confetti_gold, 10000) .setSpeedRange(0.2f, 0.5f) .setFadeOut(8000) .oneShot(statusView, 100); new ParticleSystem(this, 200, R.drawable.confetti_black, 10000) .setSpeedRange(0.2f, 0.5f) .setFadeOut(8000) .oneShot(statusView, 100); // If we're the one IT and won, send it back! if (taggedStats.getisIT()){ taggedStats.setisIT(false); // Give ourselves 10% more points as a reward! taggedStats.setTagPoints((int) ((taggedStats.getTagPoints() * .1) + taggedStats.getTagPoints()) ); // Send the data to the database. myDBRef.child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_isIT)).setValue(taggedStats.getisIT()); myDBRef.child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_tagPoints)).setValue(taggedStats.getTagPoints()); // Now set their end of things so they can't "cheat" by trying to close the window or app before it does anything. // Since update 8.6.5, we need to get the user's last known location in case they tagged us from far away. DatabaseReference uidRef = mDatabase.getReference(getString(R.string.firebase_uid_path)).child(challengeIndex); uidRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ try { String theirLong = dataSnapshot.child(getString(R.string.firebase_lastKnownLong)).getValue(String.class); String theirLat = dataSnapshot.child(getString(R.string.firebase_lastKnownLat)).getValue(String.class); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_taggedBy)).setValue(userName); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_taggedByUID)).setValue(firebaseAuthUID); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_isIT)).setValue(true); } catch (Exception e){ sendMessageToLogs("Error", TAG, e.getStackTrace()[0].getLineNumber(), e.getMessage()); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } // Try to unlock the challenge achievement. try { GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(getApplicationContext()); if (account != null){ try { Games.getAchievementsClient(this, account) .unlock(getString(R.string.achievement_not_today)); } catch (Exception e){ sendMessageToLogs("Debug", TAG, Thread.currentThread().getStackTrace()[0].getLineNumber(), "Not Today - Possibly already has it."); } } } catch (Exception e){ sendMessageToLogs("Error", TAG, e.getStackTrace()[0].getLineNumber(), e.getMessage()); } runOnUiThread(new Runnable() { @Override public void run() { finalCounter(); } }); } private void iLose(){ statusView.setTextAppearance(R.style.ChallengeScreenLoserStyle); statusView.setText(R.string.challenge_rps_youLose); // If we weren't IT and lost, accept our fate. if (!taggedStats.getisIT()){ taggedStats.setUsersTagged(taggedStats.getUsersTagged() - 1); taggedStats.setisIT(true); // Remove 10% of our points as a punishment! taggedStats.setTagPoints((int) (taggedStats.getTagPoints() - (taggedStats.getTagPoints() / 10)) ); } runOnUiThread(new Runnable() { @Override public void run() { finalCounter(); } }); } private void iWinByForfeit(){ try { countDownTimer.cancel(); countDown.setVisibility(View.INVISIBLE); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } statusView.setTextAppearance(R.style.ChallengeScreenWinnerStyle); statusView.setText(R.string.challenge_forfeit); hasGameEnded = true; scoreLayout.setVisibility(View.GONE); try { if (taggedStats.getisIT()){ taggedStats.setisIT(false); // Give ourselves 10% more points as a reward! taggedStats.setTagPoints((int) ((taggedStats.getTagPoints() * .1) + taggedStats.getTagPoints()) ); // Send the data to the database. myDBRef.child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_isIT)).setValue(taggedStats.getisIT()); myDBRef.child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_tagPoints)).setValue(taggedStats.getTagPoints()); // Now set their end of things so they can't "cheat" by trying to close the window or app before it does anything. // Since update 8.6.5, we need to get the user's last known location in case they tagged us from far away. DatabaseReference uidRef = mDatabase.getReference(getString(R.string.firebase_uid_path)).child(challengeIndex); uidRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot dataSnapshot) { if (dataSnapshot.exists()){ try { String theirLong = dataSnapshot.child(getString(R.string.firebase_lastKnownLong)).getValue(String.class); String theirLat = dataSnapshot.child(getString(R.string.firebase_lastKnownLat)).getValue(String.class); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_taggedBy)).setValue(userName); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_taggedByUID)).setValue(firebaseAuthUID); usersRef.child(theirLong).child(theirLat).child(challengeIndex).child(getString(R.string.firebase_tagged_stats)).child(getString(R.string.firebase_isIT)).setValue(true); } catch (Exception e){ sendMessageToLogs("Error", TAG, e.getStackTrace()[0].getLineNumber(), e.getMessage()); } } } @Override public void onCancelled(@NonNull DatabaseError databaseError) { } }); } } catch (Exception e){ e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { finalCounter(); } }); } private void finalCounter(){ int countdown = 5 * 1000; // 5 seconds statusView.setVisibility(View.VISIBLE); countDown.setVisibility(View.GONE); try { countDownTimer.cancel(); countDown.setVisibility(View.INVISIBLE); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } countDownTimer = new CountDownTimer(countdown, 10) { public void onTick(long millisUntilFinished) { if(millisUntilFinished <= 5000 && millisUntilFinished > 4000) { // TODO There's probably a much more efficient way to do this. title.setText(String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_ending), 5)); } else if (millisUntilFinished <= 4000 && millisUntilFinished > 3000) { title.setText(String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_ending), 4)); } else if(millisUntilFinished <= 3000 && millisUntilFinished > 2000) { title.setText(String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_ending), 3)); } else if(millisUntilFinished <= 2000 && millisUntilFinished > 1000) { title.setText(String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_ending), 2)); } else if (millisUntilFinished <= 1000){ title.setText(String.format(Locale.getDefault(), "%s %d", getString(R.string.challenge_ending), 1)); } } public void onFinish() { if (amITheChallenger){ try { challengesRef.child(challengeIndex).removeValue(); } catch (Exception e){ e.printStackTrace(); } } Intent resultIntent = new Intent(); resultIntent.putExtra(getString(R.string.challenge_activity_result), "0"); setResult(Activity.RESULT_OK, resultIntent); finish(); } }.start(); } @Override public void onBackPressed() { super.onBackPressed(); try { countDownTimer.cancel(); countDown.setVisibility(View.INVISIBLE); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } if (hasGameEnded){ try { challengesRef.child(challengeIndex).removeValue(); } catch (Exception e){ e.printStackTrace(); } Intent resultIntent = new Intent(); resultIntent.putExtra(getString(R.string.challenge_activity_result), "0"); setResult(Activity.RESULT_OK, resultIntent); } else { if (amITheChallenger){ challengesRef.child(challengeIndex).child(getString(R.string.firebase_p1_checkin)).setValue(false); } else { challengesRef.child(challengeIndex).child(getString(R.string.firebase_p2_checkin)).setValue(false); } } finish(); } @Override protected void onStop() { // Called when the activity is no longer visible to the user. super.onStop(); try { countDownTimer.cancel(); } catch (Exception e){ Log.e(TAG, "Failed to stop timer."); } if (hasGameEnded){ if (amITheChallenger){ try { challengesRef.child(challengeIndex).removeValue(); } catch (Exception e){ e.printStackTrace(); } } Intent resultIntent = new Intent(); resultIntent.putExtra(getString(R.string.challenge_activity_result), "0"); setResult(Activity.RESULT_OK, resultIntent); } else { if (amITheChallenger){ challengesRef.child(challengeIndex).child(getString(R.string.firebase_p1_checkin)).setValue(false); challengesRef.child(challengeIndex).removeValue(); } else { challengesRef.child(challengeIndex).child(getString(R.string.firebase_p2_checkin)).setValue(true); } } } }