From f44363df9bc92b0e82c893a2e6cee9077fa13114 Mon Sep 17 00:00:00 2001 From: kailando <94944679+kailando@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:24:07 -0500 Subject: [PATCH 1/5] Patch 2 (#4) * Adding PowerApps Notes * Notes and new util * Added jigglers * User profile tools * ogv > ft for remote command * New stuff around teams and gpos * Use access.log from logon script instead-reliable * Reorganized, dotsource boilerplate, bug fixes * Reorganized, dotsource boilerplate, bug fixes * Rename for verb matching * Fixed registry pointing * Changed erroraction * Added AD Queries * Added notes * Extra tools * Beginning PIM work * New utility for multi domain pass change w keepass * Migrated tool to dot source * logon to checking * Added domain trust repair note * Notes on AD restoration * Added wp plugin command * Citrix ps entry * Add logging support AuditFileSystem.py and exit nonzero on command line issues Overview: - Added support for logging - Exits nonzero on command line arg issues --------- Co-authored-by: Xattle --- FileAudit/AuditFileSystem.py | 31 ++- PowerApps/Notes.txt | 17 ++ Queries/AD/ComputerQuery.xml | 1 + Queries/AD/GroupQuery.xml | 1 + Queries/AD/UserQuery.xml | 1 + SmallUtilities/All-In-One start.txt | 8 + .../Get-CiscoPhoneStreams.ps1 | 12 +- .../{ => Dot-Sourceable}/Get-LoggedInUser.ps1 | 11 + .../Dot-Sourceable/Get-NetSpeed.ps1 | 58 ++++ .../Dot-Sourceable/Get-ServiceUptime.ps1 | 45 ++++ .../Dot-Sourceable/Remove-TeamsProfiles.ps1 | 74 ++++++ .../Update-DomainListPasswords.ps1 | 88 +++++++ .../Dot-Sourceable/Update-SecurityGroups.ps1 | 55 ++++ SmallUtilities/Get-LogonToErrors.ps1 | 26 ++ SmallUtilities/NotifyWhenOnline.ps1 | 18 ++ SmallUtilities/OneLiners.txt | 71 ++++- SmallUtilities/PSImports.ps1 | 248 ------------------ SmallUtilities/Remove-AllUserProfiles.ps1 | 18 ++ SmallUtilities/WifiUpdateScheduledTask.ps1 | 51 ++++ UsefulTools.md | 15 ++ 20 files changed, 588 insertions(+), 261 deletions(-) create mode 100644 PowerApps/Notes.txt create mode 100644 Queries/AD/ComputerQuery.xml create mode 100644 Queries/AD/GroupQuery.xml create mode 100644 Queries/AD/UserQuery.xml create mode 100644 SmallUtilities/All-In-One start.txt rename SmallUtilities/{ => Dot-Sourceable}/Get-CiscoPhoneStreams.ps1 (94%) rename SmallUtilities/{ => Dot-Sourceable}/Get-LoggedInUser.ps1 (91%) create mode 100644 SmallUtilities/Dot-Sourceable/Get-NetSpeed.ps1 create mode 100644 SmallUtilities/Dot-Sourceable/Get-ServiceUptime.ps1 create mode 100644 SmallUtilities/Dot-Sourceable/Remove-TeamsProfiles.ps1 create mode 100644 SmallUtilities/Dot-Sourceable/Update-DomainListPasswords.ps1 create mode 100644 SmallUtilities/Dot-Sourceable/Update-SecurityGroups.ps1 create mode 100644 SmallUtilities/Get-LogonToErrors.ps1 create mode 100644 SmallUtilities/NotifyWhenOnline.ps1 delete mode 100644 SmallUtilities/PSImports.ps1 create mode 100644 SmallUtilities/Remove-AllUserProfiles.ps1 create mode 100644 SmallUtilities/WifiUpdateScheduledTask.ps1 create mode 100644 UsefulTools.md diff --git a/FileAudit/AuditFileSystem.py b/FileAudit/AuditFileSystem.py index 4ccf90b..0842983 100755 --- a/FileAudit/AuditFileSystem.py +++ b/FileAudit/AuditFileSystem.py @@ -1,6 +1,7 @@ #import stuff :P import csv, sys, argparse, os, textwrap, subprocess from prettytable import PrettyTable +import logging #Default variable starts for later scan_things = False @@ -8,6 +9,17 @@ output_html = False output_cli = False +#Make logger and do stuff +logger=logging.getLogger(__name__) +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +del handler #Setup argparse stuff parser = argparse.ArgumentParser( @@ -46,7 +58,7 @@ def scan_path_ps1(): subprocess.call('powershell.exe -command Set-ExecutionPolicy RemoteSigned', shell=True) subprocess.call('powershell.exe .\GenerateFileAudit.ps1', shell=True) os.remove(".\GenerateFileAudit.ps1") - print(f"Scan is now finished and raw csv outputted to {args.csvoutput}") + logger.info(f"Scan is now finished and raw csv outputted to {args.csvoutput}") def write_ps1_file(): f = open("GenerateFileAudit.ps1", "a") @@ -197,7 +209,7 @@ def html_formatter(): f1.close() f2.close() - print(f"HTML Output has been sent to: {args.outfile}") + logger.info(f"HTML Output has been sent to: {args.outfile}") def cli_table_display(): table = PrettyTable() @@ -212,6 +224,7 @@ def cli_table_display(): display_count += 1 line_count += 1 table.sortby = "User Group" + # Not switching to logger.info because this seems that it's meant to prettyprint, not log anything print(table) print() print(f'Inheritance Flag filter is active by default. Displaying {display_count} out of {line_count} entries.') @@ -219,12 +232,12 @@ def cli_table_display(): ############################## #Check for args and set bools# if args.infile is None and args.scanpath is None: - print("Please enter an infile or a scanpath argument.") - sys.exit() + logger.error("Please enter an infile or a scanpath argument.") + sys.exit(1) if args.infile is not None and args.scanpath is not None: - print("Choose infile or scanpath. Not both.") - sys.exit() + logger.error("Choose infile or scanpath, not both") + sys.exit(1) if args.scanpath is not None: scan_things = True @@ -235,7 +248,7 @@ def cli_table_display(): elif ".html" in args.outfile: output_html = True else: - Print("Outfile is not specified or not .csv/.html. Outputting to CLI.") + logger.warn("Outfile is not specified or not .csv/.html. Outputting to CLI.") output_cli = True ############################## @@ -244,7 +257,7 @@ def cli_table_display(): scan_path_ps1() if output_csv == True: - Print("CSV Output not set up!! Displaying CLI Output.") + logger.warn("CSV Output not set up!! Displaying CLI Output.") cli_table_display() elif output_html == True: html_formatter() @@ -253,4 +266,4 @@ def cli_table_display(): if args.generate is True: write_ps1_file() - print("GenerateFileAudit.ps1 has been created.") + logger.info("GenerateFileAudit.ps1 has been created.") diff --git a/PowerApps/Notes.txt b/PowerApps/Notes.txt new file mode 100644 index 0000000..7a40813 --- /dev/null +++ b/PowerApps/Notes.txt @@ -0,0 +1,17 @@ +Dynamic Widths + Full + If(App.Width > 640, Parent.Width / 2, Parent.Width) + Half + If(App.Width > 640, Parent.Width / 4, Parent.Width /2) + Var based + OnStart + Set(FullWidthDivisor, 2); + Set(HalfWidthDivisor, 4); + Set(ScreenSplitWidth, 640); + Full w var + If(App.Width > ScreenSplitWidth, Parent.Width / FullWidthDivisor, Parent.Width) + Half w var + If(App.Width > ScreenSplitWidth, Parent.Width / HalfWidthDivisor, Parent.Width / 2) + +New Submission - Hide unless option is chose + DataCardValue8.Selected.Value="Yes" \ No newline at end of file diff --git a/Queries/AD/ComputerQuery.xml b/Queries/AD/ComputerQuery.xml new file mode 100644 index 0000000..ba80075 --- /dev/null +++ b/Queries/AD/ComputerQuery.xml @@ -0,0 +1 @@ +All Computers-1(&(objectCategory=computer)(name=*))FALSE{44170AFE-E5E7-4842-8FC4-69E3C5935D33}050000000c00000043006f006d006d006f006e00510075006500720079000000020000000308000000480061006e0064006c00650072000000100000005ee6238ac231d011891c00a024ab2dbb030500000046006f0072006d00000010000000cbe7168cc2172947a6698474d6712b81080000004400730051007500650072007900000002000000010900000056006900650077004d006f0064006500000004130000010d00000045006e00610062006c006500460069006c007400650072000000000000002a00000028006f0062006a00650063007400430061007400650067006f00720079003d0070006500720073006f006e00290028006f0062006a0065006300740043006c006100730073003d0075007300650072002900000005000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b00000000000000010f0000004e006f006e0045007800700050007700640043006800650063006b00000000000000010f0000004c006100730074004c006f0067006f006e0043006f006d0062006f000000000000001a00000028006f0062006a00650063007400430061007400650067006f00720079003d0063006f006d00700075007400650072002900000003000000010a0000004e0061006d00650043006f006d0062006f0000000c200000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b000000000000001700000028006f0062006a00650063007400430061007400650067006f00720079003d00670072006f00750070002900000002000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000 diff --git a/Queries/AD/GroupQuery.xml b/Queries/AD/GroupQuery.xml new file mode 100644 index 0000000..2ca0ba8 --- /dev/null +++ b/Queries/AD/GroupQuery.xml @@ -0,0 +1 @@ +All Groups-1(&(objectCategory=group)(name=*))FALSE{065F0E25-75CB-44B7-BE21-AD75AB48E30A}050000000c00000043006f006d006d006f006e00510075006500720079000000020000000308000000480061006e0064006c00650072000000100000005ee6238ac231d011891c00a024ab2dbb030500000046006f0072006d00000010000000cbe7168cc2172947a6698474d6712b81080000004400730051007500650072007900000002000000010900000056006900650077004d006f0064006500000004130000010d00000045006e00610062006c006500460069006c007400650072000000000000002a00000028006f0062006a00650063007400430061007400650067006f00720079003d0070006500720073006f006e00290028006f0062006a0065006300740043006c006100730073003d0075007300650072002900000005000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b00000000000000010f0000004e006f006e0045007800700050007700640043006800650063006b00000000000000010f0000004c006100730074004c006f0067006f006e0043006f006d0062006f000000000000001a00000028006f0062006a00650063007400430061007400650067006f00720079003d0063006f006d00700075007400650072002900000003000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b000000000000001700000028006f0062006a00650063007400430061007400650067006f00720079003d00670072006f00750070002900000002000000010a0000004e0061006d00650043006f006d0062006f0000000c200000010a000000440065007300630043006f006d0062006f00000000000000 diff --git a/Queries/AD/UserQuery.xml b/Queries/AD/UserQuery.xml new file mode 100644 index 0000000..92f3c37 --- /dev/null +++ b/Queries/AD/UserQuery.xml @@ -0,0 +1 @@ +All Users-1(&(objectCategory=person)(objectClass=user)(name=*))FALSE{998FE675-1E9E-4A50-8BF6-A806C5045B53}050000000c00000043006f006d006d006f006e00510075006500720079000000020000000308000000480061006e0064006c00650072000000100000005ee6238ac231d011891c00a024ab2dbb030500000046006f0072006d00000010000000cbe7168cc2172947a6698474d6712b81080000004400730051007500650072007900000002000000010900000056006900650077004d006f0064006500000004130000010d00000045006e00610062006c006500460069006c007400650072000000000000002a00000028006f0062006a00650063007400430061007400650067006f00720079003d0070006500720073006f006e00290028006f0062006a0065006300740043006c006100730073003d0075007300650072002900000005000000010a0000004e0061006d00650043006f006d0062006f0000000c200000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b00000000000000010f0000004e006f006e0045007800700050007700640043006800650063006b00000000000000010f0000004c006100730074004c006f0067006f006e0043006f006d0062006f000000000000001a00000028006f0062006a00650063007400430061007400650067006f00720079003d0063006f006d00700075007400650072002900000003000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000010d000000440069007300610062006c00650043006800650063006b000000000000001700000028006f0062006a00650063007400430061007400650067006f00720079003d00670072006f00750070002900000002000000010a0000004e0061006d00650043006f006d0062006f00000000000000010a000000440065007300630043006f006d0062006f00000000000000 diff --git a/SmallUtilities/All-In-One start.txt b/SmallUtilities/All-In-One start.txt new file mode 100644 index 0000000..0691e4b --- /dev/null +++ b/SmallUtilities/All-In-One start.txt @@ -0,0 +1,8 @@ +#Run following on the backup restore page from console command replacing FILENAME.wpress with the target backup + +var filename = 'FILENAME.wpress'; +var importer = new Ai1wm.Import(); +var storage = Ai1wm.Util.random(12); +var options = Ai1wm.Util.form('#ai1wm-backups-form').concat({name: 'storage', value: storage}).concat({name: 'archive', value: filename}); +importer.setParams(options); +importer.start(); \ No newline at end of file diff --git a/SmallUtilities/Get-CiscoPhoneStreams.ps1 b/SmallUtilities/Dot-Sourceable/Get-CiscoPhoneStreams.ps1 similarity index 94% rename from SmallUtilities/Get-CiscoPhoneStreams.ps1 rename to SmallUtilities/Dot-Sourceable/Get-CiscoPhoneStreams.ps1 index c55c162..0b0973d 100644 --- a/SmallUtilities/Get-CiscoPhoneStreams.ps1 +++ b/SmallUtilities/Dot-Sourceable/Get-CiscoPhoneStreams.ps1 @@ -136,8 +136,16 @@ function Get-CiscoPhoneStreams return ConvertFrom-Csv $results } -Write-Output "To run this script, dot-source the file using . .\Get-CiscoPhoneStreams.ps1 then run Get-Help Get-CiscoPhoneStreams" - +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Get-CiscoPhoneStreams function + Get-CiscoPhoneStreams @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Get-CiscoPhoneStreams.ps1 then run Get-Help Get-CiscoPhoneStreams for more details." + } +} # # Notes: The following are the array ID of the Stream stat, the stream stat name, and the +1 in the array for that stat which should correspond to its value # 1,Device logs,Streaming statistics diff --git a/SmallUtilities/Get-LoggedInUser.ps1 b/SmallUtilities/Dot-Sourceable/Get-LoggedInUser.ps1 similarity index 91% rename from SmallUtilities/Get-LoggedInUser.ps1 rename to SmallUtilities/Dot-Sourceable/Get-LoggedInUser.ps1 index 2fa9dc5..af1503d 100644 --- a/SmallUtilities/Get-LoggedInUser.ps1 +++ b/SmallUtilities/Dot-Sourceable/Get-LoggedInUser.ps1 @@ -102,4 +102,15 @@ function Get-LoggedInUser $out += $users } Write-Output $out +} + +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Get-LoggedInUser function + Get-LoggedInUser @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Get-LoggedInUser.ps1 then run Get-Help Get-LoggedInUser for more details." + } } \ No newline at end of file diff --git a/SmallUtilities/Dot-Sourceable/Get-NetSpeed.ps1 b/SmallUtilities/Dot-Sourceable/Get-NetSpeed.ps1 new file mode 100644 index 0000000..0af6a6d --- /dev/null +++ b/SmallUtilities/Dot-Sourceable/Get-NetSpeed.ps1 @@ -0,0 +1,58 @@ +function Get-NetSpeed { + #SIZE OF SPECIFIED FILE IN MB (10 or 100) + $size = 100 + + #FILE TO DOWNLOAD + $downloadUrl = "http://ipv4.download.thinkbroadband.com/$($size)MB.zip" + $uploadUrl = "http://ipv4.download.thinkbroadband.com/$($size)MB.zip" + + #WHERE TO STORE DOWNLOADED FILE + $localfile = "$($env:TEMP)/$($size)MB.zip" + + Write-Output "$($size)MB test started at $(get-date -Format "HH:mm:ss MM/dd/yyyy")" + + #RUN DOWNLOAD + $webclient = New-Object System.Net.WebClient + $webclient.Headers.Add("User-Agent: Other") + $downloadstart_time = Get-Date + $webclient.DownloadFile($downloadurl, $localfile) + + #CALCULATE DOWNLOAD SPEED + $downloadtimetaken = $((Get-Date).Subtract($downloadstart_time).Seconds) + $downloadspeed = ($size / $downloadtimetaken)*8 + Write-Output "Time taken: $downloadtimetaken second(s) | Download Speed: $downloadspeed mbps" + + #RUN UPLOAD + $uploadstart_time = Get-Date + $webclient.UploadFile($UploadURL, $localfile) > $null; + + #CALCULATE UPLOAD SPEED + $uploadtimetaken = $((Get-Date).Subtract($uploadstart_time).Seconds) + $uploadspeed = ($size / $uploadtimetaken) * 8 + Write-Output "Upload currently broken. Need to find site to allow for upload testing" + Write-Output "Time taken: $uploadtimetaken second(s) | Upload Speed: $uploadspeed mbps" + + #DELETE TEST DOWNLOAD FILE + Remove-Item –path $localfile + +} + +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Get-NetSpeed function + Get-NetSpeed @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Get-NetSpeed.ps1 then run Get-Help Get-NetSpeed for more details." + Write-Output "Work In Process - Download works but upload is broken. Script runs clean." + } +} + +# # Small download version +# function Get-NetworkSpeed { +# $a=Get-Date +# Invoke-WebRequest http://ipv4.download.thinkbroadband.com/10MB.zip|Out-Null +# $output = "$((10/((Get-Date)-$a).TotalSeconds)*8) Mbps Download" +# return $output +# } diff --git a/SmallUtilities/Dot-Sourceable/Get-ServiceUptime.ps1 b/SmallUtilities/Dot-Sourceable/Get-ServiceUptime.ps1 new file mode 100644 index 0000000..683cbca --- /dev/null +++ b/SmallUtilities/Dot-Sourceable/Get-ServiceUptime.ps1 @@ -0,0 +1,45 @@ +function Get-ServiceUptime +{ + <# + .SYNOPSIS + Gets the uptime for a specific service on the machine + + .DESCRIPTION + Uses Get-CimInstance to identify the process connected to a service. Then pulls the creation date and calculates uptimes of the service based on that. + + .PARAMETER Name + Service name + + .EXAMPLE + PS C:\> ServiceUptime -Name wuauserv + Shows the uptime for the wuauserv service + #> + +[CmdletBinding()] + param( + [string]$Name + ) + + # Prepare name filter for WQL + $Name = $Name -replace "\\","\\" -replace "'","\'" -replace "\*","%" + + # Fetch service instance + $Service = Get-CimInstance -ClassName Win32_Service -Filter "Name LIKE '$Name'" + + # Use ProcessId to fetch corresponding process + $Process = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($Service.ProcessId)" + + # Calculate uptime and return + return (Get-Date) - $Process.CreationDate +} + +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Get-ServiceUptime function + Get-ServiceUptime @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Get-ServiceUptime.ps1 then run Get-Help Get-ServiceUptime for more details." + } +} diff --git a/SmallUtilities/Dot-Sourceable/Remove-TeamsProfiles.ps1 b/SmallUtilities/Dot-Sourceable/Remove-TeamsProfiles.ps1 new file mode 100644 index 0000000..56f381c --- /dev/null +++ b/SmallUtilities/Dot-Sourceable/Remove-TeamsProfiles.ps1 @@ -0,0 +1,74 @@ +# Finds profiles that haven't logged in in the last 14 days and removes teams from appdata and registry. +# When using Teams Machine-Wide Installer, this will enable a short reinstall of the latest version on next login. +# Can take -DaysSinceLastLogin as a parameter. Use -1 +function Remove-TeamsProfiles { + <# + .SYNOPSIS + Cleans up Microsoft Teams installations for user profiles based on last login time. Uses Active Directory for SID so will need adjustments for local-only profiiles. + + NOTE: Because of limitations using ntuser.dat or other methods of tracking when a profile was last used, this script relies on checking %appdata%\access.log for the last write time. This doesn't exist normally but can be easily added to an environment through a logon script GPO or task that runs "echo Logon %date% %time% > %APPDATA%\access.log" If access.log doesn't exist, all teams profiles will be deleted. + + .DESCRIPTION + This script finds user profiles that haven't logged in within the specified number of days and removes Microsoft Teams from their AppData folders and registry entries. You can set the number of days for the last login using the -DaysSinceLastLogin parameter. If you want to clean all profiles regardless of their last login time, you can use -1 for -DaysSinceLastLogin. + + .PARAMETER DaysSinceLastLogin + Specifies the number of days since the last login. Profiles that haven't logged in within this period will have Microsoft Teams removed. Use -1 to clean all profiles regardless of their last login time. Default value is 14. + + .EXAMPLE + .\Cleanup-Teams.ps1 -DaysSinceLastLogin 30 + Cleans up Teams installations for profiles that haven't logged in within the last 30 days. + + .EXAMPLE + .\Cleanup-Teams.ps1 -DaysSinceLastLogin -1 + Cleans up Teams installations for all profiles, regardless of their last login time. + #> + + [CmdletBinding()] + param ( + [int]$DaysSinceLastLogin = 14 + ) + + # Get the current date + $CurrentDate = Get-Date + + # Get a list of user profile folders + $UserProfileFolders = Get-ChildItem -Path C:\Users -Directory + + foreach ($UserProfileFolder in $UserProfileFolders) { + $UserProfilePath = $UserProfileFolder.FullName + $LastModifiedTime = $(Get-Item "$($UserProfileFolder.FullName)\Appdata\Roaming\access.log" -ErrorAction SilentlyContinue).LastWriteTime + + # Check if the user hasn't logged in within the specified days + if (-not $LastModifiedTime -or ($CurrentDate - $LastModifiedTime).Days -gt $DaysSinceLastLogin) { + try { + # Remove Teams directories + Remove-Item -Path "$UserProfilePath\AppData\Local\Microsoft\Teams" -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$UserProfilePath\AppData\Local\Microsoft\TeamsMeetingAddin" -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item -Path "$UserProfilePath\AppData\Local\Microsoft\TeamsPresenceAddin" -Recurse -Force -ErrorAction SilentlyContinue + + # Remove Teams registry entries + $UserSID = $(Get-ADUser -Identity $UserProfileFolder.Name).SID + Remove-Item -Path "Registry::HKEY_USERS\$UserSID\Software\Microsoft\Office\Teams" -Recurse -Force -ErrorAction SilentlyContinue + + # Log successful cleanup + Write-Output "Successfully cleaned up Teams for $($UserProfileFolder.Name)" + } catch { + # Log any errors + Write-Output "Error cleaning up Teams for $($UserProfileFolder.Name): $_" + } + } else { + Write-Output "Cleanup not needed for $($UserProfileFolder.Name)" + } + } +} + +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Remove-TeamsProfiles function + Remove-TeamsProfiles @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Remove-TeamsProfiles.ps1 then run Get-Help Remove-TeamsProfiles for more details." + } +} diff --git a/SmallUtilities/Dot-Sourceable/Update-DomainListPasswords.ps1 b/SmallUtilities/Dot-Sourceable/Update-DomainListPasswords.ps1 new file mode 100644 index 0000000..c3c13de --- /dev/null +++ b/SmallUtilities/Dot-Sourceable/Update-DomainListPasswords.ps1 @@ -0,0 +1,88 @@ +function Update-DomainListPasswords { +<# + .SYNOPSIS + Changes passwords in a list of domains based from a CSV file semi-automatically + + .DESCRIPTION + Must use a CSV file with the following headers: + domain, server, note + + Cycles through the list of servers on various domains based on the CSV file. + When ran, it checks the CSV and confirms if you want to connect to the first server in the list. + Then it asks for a username and password (@domain is automatically appended from the CSV). + The credentials are used to login to the server specified. Server must have powershell remoting enabled. + Once logged in, prompts for the username to reset and the new password. DOES NOT CONFIRM PASSWORD TWICE + Immedeately tries to set password for the user using Set-ADAccountPassword. Server needs to have ActiveDirectory powershell module for this to work. + Logs out of domain and moves to the next server in the list. + + Will only do test runs unless -Live is included. Test runs work the same but using Set-ADAccountPassword -WhatIf. + + .PARAMETER ServerList + Relative location and filename of the server list CSV. + Must be formatted with the following headers: + domain, server, note + + .PARAMETER Live + Include to actually change passwords. If NOT included, script will run using -WhatIf and no passwords will be changed. + + .PARAMETER AlwaysConnect + Ignores the confirmation for each server and jumps to requesting credentials. + Makes it harder to break execution in case of error. + + .EXAMPLE + PS C:\> Update-DomainListPasswords -ServerList .\ListOfServers.csv + Runs a test run without changing passwords. Good for confirming hostnames, domains, and powershell remoting is working as expected. + + .EXAMPLE + PS C:\> Update-DomainListPasswords -ServerList .\ListOfServers.csv -Live -AlwaysConnect + Goes through the CSV list and changes passwords as documented. Does not prompt before connecting to each server. Not automatic. Quicker than logging into 20 different domains. + +#> + + [CmdletBinding()] + + param( + [Parameter(Mandatory=$true)][string]$ServerList, + [switch]$Live, + [switch]$AlwaysConnect + ) + + $pathToCSV = $ServerList + $csvFile = Import-CSV -Path $pathToCSV + $shouldConnect = 'n' + foreach ($entry in $csvFile) { + + if($AlwaysConnect) { + $shouldConnect = 'y' + } else { + $shouldConnect = Read-Host "Connect to $($entry.server).$($entry.domain) ($($entry.note))? [y]es or [n]o" + } + + if ($shouldConnect -eq 'y') { + $loginCred = Get-Credential -Message "Please enter login credentials for server $($entry.server) on domain $($entry.domain) ($($entry.note))." + $fullLoginCred = new-object -typename System.Management.Automation.PSCredential -argumentlist "$($loginCred.username)@$($entry.domain)",$loginCred.password + $s = New-PSSession -ComputerName "$($entry.server).$($entry.domain)" -Credential $fullLoginCred + + if ($Live) { + Invoke-Command -Session $s -Scriptblock { + $newCred = Get-Credential -Message "Please enter username to lookup and NEW password" + Set-ADAccountPassword -Identity $newCred.username -NewPassword $newCred.password -Reset + } + Remove-PSSession $s + Write-Host "Password changed for user on $($entry.domain) ($($entry.note))!" + } else { + Invoke-Command -Session $s -Scriptblock { + $newCred = Get-Credential -Message "Please enter username to lookup and NEW password" + Set-ADAccountPassword -WhatIf -Identity $newCred.username -NewPassword $newCred.password -Reset + } + Remove-PSSession $s + Write-Host "Test scenario ran on $($entry.domain) ($($entry.note))!" + } + + } else { + Write-Output "Skipping $($entry.server).$($entry.domain) ($($entry.note))." + } + $shouldConnect = 'n' + } + Write-Output "All CSV entries have been iterated through in file $($pathToCSV)" +} \ No newline at end of file diff --git a/SmallUtilities/Dot-Sourceable/Update-SecurityGroups.ps1 b/SmallUtilities/Dot-Sourceable/Update-SecurityGroups.ps1 new file mode 100644 index 0000000..9750bf0 --- /dev/null +++ b/SmallUtilities/Dot-Sourceable/Update-SecurityGroups.ps1 @@ -0,0 +1,55 @@ +function Update-SecurityGroups { + <# + .SYNOPSIS + Refreshes Kerberos tickets updating computer security group listings + + .DESCRIPTION + Uses the klist commands as well as gpupdate to refresh the kerberos tickets for the computer. Has to be ran as admin and only works on remote devices if Powershell Remoting is enabled. + + .PARAMETER ComputerName + Computer to refresh tickets on. + + .EXAMPLE + PS C:\> Update-SecurityGroups -ComputerName TARGETHOSTNAME + #> + +[CmdletBinding()] + param( + [Parameter( + Position = 0, + ValueFromPipeline = $true, + ValueFromPipelineByPropertyName = $true + )] + [Alias('ComputerName')] + [string]$Name = $env:COMPUTERNAME + ) + + process { + $computer = $Name + if ($computer -eq $env:COMPUTERNAME) { + Write-Output "Running locally on $computer" + # klist.exe sessions | findstr /i $env:COMPUTERNAME + # klist.exe -li 0x3e7 purge + # gpupdate /force + } else { + Write-Output "Connecting to $computer" + Enter-PSSession -ComputerName $computer + # klist.exe sessions | findstr /i $env:COMPUTERNAME + # klist.exe -li 0x3e7 purge + # gpupdate /force + Exit-PSSession + } + } + +} + +if ($(Split-Path $MyInvocation.InvocationName -Leaf) -eq $MyInvocation.MyCommand) { + try { + # If so, run the Update-SecurityGroups function + Update-SecurityGroups @args + + } + catch { + Write-Output "This script can be dot-sourced using using . .\Update-SecurityGroups.ps1 then run Get-Help Update-SecurityGroups for more details." + } +} diff --git a/SmallUtilities/Get-LogonToErrors.ps1 b/SmallUtilities/Get-LogonToErrors.ps1 new file mode 100644 index 0000000..a8b12c5 --- /dev/null +++ b/SmallUtilities/Get-LogonToErrors.ps1 @@ -0,0 +1,26 @@ +$accountToCheck = '' +$badWorkstations = '' +$validWorkstations = '' + +foreach ($workstation in $(get-aduser -Identity $accountToCheck -Properties logonworkstations).logonworkstations -split ",") { + Write-Output "Checking $workstation..." + try { + Get-ADComputer -Identity $workstation | out-null + $validWorkstations = $validWorkstations + ",$workstation" + } + catch { + Write-Output "$workstation errored out" + $badWorkstations = $badWorkstations + ",$workstation" + } +} + +try {$validWorkstations = $validWorkstations.substring(1)} +catch {Write-Output "No valid workstations."} +try {$badWorkstations = $badWorkstations.substring(1)} +catch {Write-Output "No bad workstations."} + +Write-Output "validWorkstations: $validWorkstations" +Write-Output "badWorkstations: $badWorkstations" + +Write-Output "User account can be updated by running the following command without -WhatIf:" +Write-Output "set-aduser -WhatIf -Identity $accountToCheck -LogonWorkstations '$validWorkstations'" diff --git a/SmallUtilities/NotifyWhenOnline.ps1 b/SmallUtilities/NotifyWhenOnline.ps1 new file mode 100644 index 0000000..00ede51 --- /dev/null +++ b/SmallUtilities/NotifyWhenOnline.ps1 @@ -0,0 +1,18 @@ +$computerName = "vdmc-train1" + +while ($true) { + # Test the connection to the specified computer + $connectionTest = Test-NetConnection -ComputerName $computerName -InformationLevel Quiet + + # Check if the computer is online + if ($connectionTest) { + # Computer is online, display a pop-up message + $wshell = New-Object -ComObject Wscript.Shell + $wshell.Popup("The computer '$computerName' is online.", 0, "Computer Status", 64) + break # Exit the loop once the computer is online + } + else { + # Computer is offline, wait for a while before testing again + Start-Sleep -Seconds 10 + } +} diff --git a/SmallUtilities/OneLiners.txt b/SmallUtilities/OneLiners.txt index a98f0b9..a99dc0c 100644 --- a/SmallUtilities/OneLiners.txt +++ b/SmallUtilities/OneLiners.txt @@ -1,5 +1,6 @@ This file is just a list of useful one-liners or similarly small fragments that don't even fall under a small utility. Each should have a short description as well as what environment/shell to use it in. +Also includes cool things to revisit and examples of commands that are forgettable if not frequently used. CMD @@ -14,6 +15,13 @@ CMD No password needed if running command prompt as system user (PsExec.exe -s) tscon SESSIONID /DEST:CURRENTSESSION + Check group policy + Gpresult /r /scope computer + gpresult /r /scope user + + Log when a user logs in. Works nicely with Logon scripts GPO to setup a way to track profile logins on a local machine. + echo Logon %date% %time% > %APPDATA%\access.log + WMIC Get software installed on machine Wmic /node:COMPUTER product get name, version, vendor @@ -33,6 +41,9 @@ Powershell Get all AD users according to OU search. Get-ADUser -Filter * |Where-Object { ($_.DistinguishedName -like "*OU=Branch*") } |Select SamAccountName + Get user profiles not used in the last 365 days + Get-CimInstance -ClassName Win32_UserProfile | Where-Object {(!$_.Special) -and ([DateTime]::Parse($_.LastUseTime) -lt (Get-Date).AddDays(-365))} | sort -Property LastUseTime + Get a csv of all files created today in the current location. Get-Childitem .\ * -Recurse -ErrorAction SilentlyContinue | Where-Object {$_.CreationTime -gt (Get-Date).Date } | Select-Object -Property Name, CreationTime, Directory |Export-CSV -Path .\output.csv @@ -65,7 +76,7 @@ Powershell $Apps = @() $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*" $Apps += Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*" - $Apps | ogv + $Apps | ft Get whether the lock screen is in use - whether computer is locked Get-Process -Name "logonui" @@ -92,4 +103,60 @@ Powershell Get external IP address nslookup myip.opendns.com. resolver1.opendns.com - Invoke-RestMethod ipinfo.io/ip \ No newline at end of file + Invoke-RestMethod ipinfo.io/ip + + Jigglers + Keyboard jiggler + $wshell = New-Object -ComObject wscript.shell; while($True){ Start-Sleep -Seconds 60; $wshell.SendKeys('{F13}') } + Mouse jiggler top left + Add-Type -AssemblyName System.Windows.Forms;while($True){[Windows.Forms.Cursor]::Position=((Get-Random 100).(Get-Random 100));Start-Sleep 60} + Mouse jiggler but anywhere on the screen + Add-Type -AssemblyName System.Windows.Forms; $w=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Width; $h=[System.Windows.Forms.Screen]::PrimaryScreen.Bounds.Height; while($True) { [Windows.Forms.Cursor]::Position = New-Object System.Drawing.Point((Get-Random -Minimum 0 -Maximum $w), (Get-Random -Minimum 0 -Maximum $h)); Start-Sleep 60 } + + ORCA - https://office365itpros.com/2019/11/14/orca-checks-office365-atp-settings/ + Install-Module -Name ORCA + Import-Module ExchangeOnlineManagement + + Get-ORCAReport + + Command to encoded command (Only works on PS V5 and up) - https://stackoverflow.com/questions/22258668/how-to-condense-powershell-script-to-fit-on-a-single-line + Turn a script into raw Base64 + $command = Get-Content .\YourPowerShellFileContainingTheCode.ps1 -raw + # Get-Content may require "-encoding utf8" or other encodings depending on your file + $encodedCommand = [System.Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($command)) + Write-Output "Text for application:" + Write-Output "PowerShell.exe" "" + Write-Output "Text for argurments:" + Write-Output "-encodedCommand $encodedCommand" + + Run base64 script using PowerShell (Works awesome for scheduled tasks and remote commands where you may not want to bother with creating a script/changing ExecutionPolicy) + PowerShell.exe -encodedCommand EncodedCommandStringOutputFromTheBase64Above + + Install Microsoft Graph PowerShell SDK + Powershell >= 5.1 + Dotnet >= 4.7 + Install-Module PowerShellGet + Get-ExecutionPolicy + Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + Install-Module Microsoft.Graph -Scope CurrentUser + Get-InstalledModule Microsoft.Graph + + + Privileged Identity Management controls + To connect and view PIM scopes (Requires M$ Graph PowerShell SDK) + Connect-MgGraph -Scopes "RoleManagement.ReadWrite.Directory" + Get-MgRoleManagementDirectoryRoleEligibilityScheduleInstance -Filter "principalId eq '$UUID'" | Format-List + + Fix domain computer trust without a full rejoin + Test-ComputerSecureChannel -Repair -Credential $cred + + Find deleted AD objects + $restoreItem = Get-ADObject -IncludeDeletedObjects -Filter * | ogv -PassThru + + Restore deleted AD objects + Restore-ADObject $restoreItem + + + Citrix + Get-BrokerAccessPolicyRule | select @{Name='DG Name';Expression={ $_.name}} -ExpandProperty includedusers | select 'DG Name',Name + Generate a list of all delivery groups and users that have access to them \ No newline at end of file diff --git a/SmallUtilities/PSImports.ps1 b/SmallUtilities/PSImports.ps1 deleted file mode 100644 index 50e0d81..0000000 --- a/SmallUtilities/PSImports.ps1 +++ /dev/null @@ -1,248 +0,0 @@ -# Adds useful Powershell functions -# Get-ServiceUptime -# Get-LoggedInUser - Powershell wrapper for query user -# Update-SecurityGroups -# Get-NetworkSpeed - - -function Get-NetworkSpeed { - $a=Get-Date - Invoke-WebRequest http://ipv4.download.thinkbroadband.com/10MB.zip|Out-Null - $output = "$((10/((Get-Date)-$a).TotalSeconds)*8) Mbps Download" - return $output -} - -function Get-NetSpeed { - #SIZE OF SPECIFIED FILE IN MB (10 or 100) - $size = 100 - - #FILE TO DOWNLOAD - $downloadUrl = "http://ipv4.download.thinkbroadband.com/$($size)MB.zip" - $uploadUrl = "http://ipv4.download.thinkbroadband.com/$($size)MB.zip" - - #WHERE TO STORE DOWNLOADED FILE - $localfile = "$($env:TEMP)/$($size)MB.zip" - - Write-Output "$($size)MB test started at $(get-date -Format "HH:mm:ss MM/dd/yyyy")" - - #RUN DOWNLOAD - $webclient = New-Object System.Net.WebClient - $webclient.Headers.Add("User-Agent: Other") - $downloadstart_time = Get-Date - $webclient.DownloadFile($downloadurl, $localfile) - - #CALCULATE DOWNLOAD SPEED - $downloadtimetaken = $((Get-Date).Subtract($downloadstart_time).Seconds) - $downloadspeed = ($size / $downloadtimetaken)*8 - Write-Output "Time taken: $downloadtimetaken second(s) | Download Speed: $downloadspeed mbps" - - #RUN UPLOAD - $uploadstart_time = Get-Date - $webclient.UploadFile($UploadURL, $localfile) > $null; - - #CALCULATE UPLOAD SPEED - $uploadtimetaken = $((Get-Date).Subtract($uploadstart_time).Seconds) - $uploadspeed = ($size / $uploadtimetaken) * 8 - Write-Output "Upload currently broken. Need to find site to allow for upload testing" - Write-Output "Time taken: $uploadtimetaken second(s) | Upload Speed: $uploadspeed mbps" - - #DELETE TEST DOWNLOAD FILE - Remove-Item –path $localfile - -} - -function Get-TestingStuff { - [CmdletBinding()] - param( - [Parameter(ValueFromPipeline)][string]$ComputerName - ) - - process { - Get-Service -ComputerName $ComputerName - } -} - -function Update-SecurityGroups { - <# - .SYNOPSIS - Refreshes Kerberos tickets updating computer security group listings - - .DESCRIPTION - Uses the klist commands as well as gpupdate to refresh the kerberos tickets for the computer. Has to be ran as admin and only works on remote devices if Powershell Remoting is enabled. - - .PARAMETER ComputerName - Computer to refresh tickets on. - - .EXAMPLE - PS C:\> Update-SecurityGroups -ComputerName TARGETHOSTNAME - #> - -[CmdletBinding()] - param( - [Parameter( - Position = 0, - ValueFromPipeline = $true, - ValueFromPipelineByPropertyName = $true - )] - [Alias('ComputerName')] - [string]$Name = $env:COMPUTERNAME - ) - - process { - $computer = $Name - if ($computer -eq $env:COMPUTERNAME) { - Write-Output "Running locally on $computer" - # klist.exe sessions | findstr /i $env:COMPUTERNAME - # klist.exe -li 0x3e7 purge - # gpupdate /force - } else { - Write-Output "Connecting to $computer" - Enter-PSSession -ComputerName $computer - # klist.exe sessions | findstr /i $env:COMPUTERNAME - # klist.exe -li 0x3e7 purge - # gpupdate /force - Exit-PSSession - } - } - -} - - -function Get-ServiceUptime -{ - <# - .SYNOPSIS - Gets the uptime for a specific service on the machine - - .DESCRIPTION - Uses Get-CimInstance to identify the process connected to a service. Then pulls the creation date and calculates uptimes of the service based on that. - - .PARAMETER Name - Service name - - .EXAMPLE - PS C:\> ServiceUptime -Name wuauserv - Shows the uptime for the wuauserv service - #> - -[CmdletBinding()] - param( - [string]$Name - ) - - # Prepare name filter for WQL - $Name = $Name -replace "\\","\\" -replace "'","\'" -replace "\*","%" - - # Fetch service instance - $Service = Get-CimInstance -ClassName Win32_Service -Filter "Name LIKE '$Name'" - - # Use ProcessId to fetch corresponding process - $Process = Get-CimInstance -ClassName Win32_Process -Filter "ProcessId = $($Service.ProcessId)" - - # Calculate uptime and return - return (Get-Date) - $Process.CreationDate -} - -function Get-LoggedInUser -{ -<# - .SYNOPSIS - Shows all the users currently logged in - - .DESCRIPTION - Shows the users currently logged into the specified computernames - - .PARAMETER ComputerName - One or more computernames - - .EXAMPLE - PS C:\> Get-LoggedInUser - Shows the users logged into the local system - - .EXAMPLE - PS C:\> Get-LoggedInUser -ComputerName server1,server2,server3 - Shows the users logged into server1, server2, and server3 - - .EXAMPLE - PS C:\> Get-LoggedInUser | where idletime -gt "1.0:0" | ft - Get the users who have been idle for more than 1 day. Format the output - as a table. - - Note the "1.0:0" string - it must be either a system.timespan datatype or - a string that can by converted to system.timespan. Examples: - days.hours:minutes - hours:minutes -#> - - [CmdletBinding()] - param - ( - [ValidateNotNullOrEmpty()] - [String[]]$ComputerName = $env:COMPUTERNAME - ) - - $out = @() - $percentComplete = 0 - $computerIter = 1 - ForEach ($computer in $ComputerName) - { - $percentComplete = [math]::Round($computerIter / $ComputerName.Count * 100) - Write-Progress -Activity "User Search in Progress" -Status "$percentComplete% Complete:" -PercentComplete $percentComplete - $computerIter++ - try { if (-not (Test-Connection -ComputerName $computer -Quiet -Count 1 -ErrorAction Stop)) { Write-Warning "Can't connect to $computer"; continue } } - catch { Write-Warning "Can't test connect to $computer"; continue } - - $quserOut = quser.exe /SERVER:$computer 2>&1 - if ($quserOut -match "No user exists") - { Write-Warning "No users logged in to $computer"; continue } - - if ($quserOut -like "*Error 0*") - { Write-Warning "Access is denied on $computer"; continue } - - - $users = $quserOut -replace '\s{2,}', ',' | - ConvertFrom-CSV -Header 'username', 'sessionname', 'id', 'state', 'idleTime', 'logonTime' | - Add-Member -MemberType NoteProperty -Name ComputerName -Value $computer -PassThru - - $users = $users[1..$users.count] - - for ($i = 0; $i -lt $users.count; $i++) - { - if ($users[$i].sessionname -match '^\d+$') - { - $users[$i].logonTime = $users[$i].idleTime - $users[$i].idleTime = $users[$i].STATE - $users[$i].STATE = $users[$i].ID - $users[$i].ID = $users[$i].SESSIONNAME - $users[$i].SESSIONNAME = $null - } - - # cast the correct datatypes - $users[$i].ID = [int]$users[$i].ID - - $idleString = $users[$i].idleTime - if ($idleString -eq '.') { $users[$i].idleTime = 0 } - if ($idleString -eq 'none') { $users[$i].idleTime = 0 } - if (!$idleString) { $users[$i].idleTime = 0 } - - # if it's just a number by itself, insert a '0:' in front of it. Otherwise [timespan] cast will interpret the value as days rather than minutes - if ($idleString -match '^\d+$') - { $users[$i].idleTime = "0:$($users[$i].idleTime)" } - - # if it has a '+', change the '+' to a colon and add ':0' to the end - if ($idleString -match "\+") - { - $newIdleString = $idleString -replace "\+", ":" - $newIdleString = $newIdleString + ':0' - $users[$i].idleTime = $newIdleString - } - - $users[$i].idleTime = [timespan]$users[$i].idleTime - if (!$users[$i].logonTime) { $users[$i].logonTime = 0 } - $users[$i].logonTime = [datetime]$users[$i].logonTime - } - $users = $users | Sort-Object -Property idleTime - $out += $users - } - Write-Output $out -} diff --git a/SmallUtilities/Remove-AllUserProfiles.ps1 b/SmallUtilities/Remove-AllUserProfiles.ps1 new file mode 100644 index 0000000..e4802b1 --- /dev/null +++ b/SmallUtilities/Remove-AllUserProfiles.ps1 @@ -0,0 +1,18 @@ +# SAVE THIS SCRIPT SO IT IS IN TESTING MODE UNTIL FLAGS FOR TESTING ARE INTRODUCED +# Get user profiles with a null LastUseTime +$profilesToDelete = Get-CimInstance -ClassName Win32_UserProfile | Where-Object { (!$_.Special) } + +# Loop through and delete the profiles and their folders +foreach ($profile in $profilesToDelete) { + $profilePath = $profile.LocalPath + Write-Host "Deleting user profile: $profilePath" + + # Remove the user profile - use WhatIf in testing mode + Remove-CimInstance -InputObject $profile -Confirm:$false -WhatIf + + # Remove the user folder if it exists + if (Test-Path -Path $profilePath -PathType Container) { + Write-Host "Deleting user folder: $profilePath" + #Remove-Item -Path $profilePath -Force -Recurse + } +} \ No newline at end of file diff --git a/SmallUtilities/WifiUpdateScheduledTask.ps1 b/SmallUtilities/WifiUpdateScheduledTask.ps1 new file mode 100644 index 0000000..a99cc92 --- /dev/null +++ b/SmallUtilities/WifiUpdateScheduledTask.ps1 @@ -0,0 +1,51 @@ +Invoke-Command -ScriptBlock { + $scheduledTaskName = "SSID Update - VDMC-Wireless Password" + $wifiKey = "Register3!Manmade" + $wifiProfileName = "VDMC-Wireless" + $filePathName = "C:\Wi-Fi-VDMC-Wireless-6-5-2023.xml" + $xmlFile = @" + + + VDMC-Wireless + + + 56444D432D576972656C657373 + $wifiProfileName + + + ESS + auto + + + + WPA2PSK + AES + false + + + passPhrase + false + $wifiKey + + + + + false + 3536636478 + + +"@ + + Write-Output $xmlFile > $filePathName + netsh wlan add profile filename=$filePathName user=all + $foundNewKey = netsh wlan show profile name=$wifiProfileName key=clear | Select-String $wifiKey + if ($foundNewKey) { + remove-item -force $filePathName + } + else { + Start-ScheduledTask $scheduledTaskName + } +} + +#Single Line (Remove #) +#Invoke-Command -ScriptBlock {$scheduledTaskName = "SSID Update - VDMC-Wireless Password";$wifiKey = "Register3!Manmade";$wifiProfileName = "VDMC-Wireless";$filePathName = "C:\Wi-Fi-VDMC-Wireless-6-5-2023.xml";$xmlFile = "VDMC-Wireless56444D432D576972656C657373VDMC-WirelessESSautoWPA2PSKAESfalsepassPhrasefalseRegister3!Manmadefalse3536636478";Write-Output $xmlFile > $filePathName;netsh wlan add profile filename=$filePathName user=all;$foundNewKey = netsh wlan show profile name=$wifiProfileName key=clear | Select-String $wifiKey;if ($foundNewKey) {remove-item -force $filePathName}else {Start-ScheduledTask $scheduledTaskName};} \ No newline at end of file diff --git a/UsefulTools.md b/UsefulTools.md new file mode 100644 index 0000000..7d97b64 --- /dev/null +++ b/UsefulTools.md @@ -0,0 +1,15 @@ +# A list of things the average Admin may want + +## Software Stuff + +- RDCMan - https://learn.microsoft.com/en-us/sysinternals/downloads/rdcman +- PSTools - https://learn.microsoft.com/en-us/sysinternals/downloads/pstools +- Microsoft Terminal - https://github.com/microsoft/terminal +- PowerToys - https://learn.microsoft.com/en-us/windows/powertoys/ +- VSCode - https://code.visualstudio.com/ +- Everything - https://www.voidtools.com/ +- WinDirStat - https://windirstat.net/ + +## Hardware Stuff + +- This screwdriver set - https://a.co/d/9w0uXY4 \ No newline at end of file From 43f930d2760a5e0e3225df9031b1cd018489c999 Mon Sep 17 00:00:00 2001 From: kailando <94944679+kailando@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:24:22 -0500 Subject: [PATCH 2/5] Patch 3 (#3) * Adding PowerApps Notes * Notes and new util * Added jigglers * User profile tools * ogv > ft for remote command * New stuff around teams and gpos * Use access.log from logon script instead-reliable * Reorganized, dotsource boilerplate, bug fixes * Reorganized, dotsource boilerplate, bug fixes * Rename for verb matching * Fixed registry pointing * Changed erroraction * Added AD Queries * Added notes * Extra tools * Beginning PIM work * New utility for multi domain pass change w keepass * Migrated tool to dot source * logon to checking * Added domain trust repair note * Notes on AD restoration * Added wp plugin command * Citrix ps entry * Add log support to MassCommand.py and add exit codes Changes: - Added logging support - Verbosed `sys.exit()` to `sys.exit(0)` - On command line errors, `sys.exit(1)` instead of `sys.exit()` (see lines 130 and 135) --------- Co-authored-by: Xattle --- MassCommand/MassCommand.py | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/MassCommand/MassCommand.py b/MassCommand/MassCommand.py index 262f37c..00f7fb6 100644 --- a/MassCommand/MassCommand.py +++ b/MassCommand/MassCommand.py @@ -1,4 +1,16 @@ -import sys, argparse, os, textwrap, subprocess, shutil, glob +import sys, argparse, os, textwrap, subprocess, shutil, glob, logger + +#Make logger and do stuff +logger=logging.getLogger(__name__) +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +del handler #Setup argparse stuff parser = argparse.ArgumentParser( @@ -40,7 +52,7 @@ def log_cleaning(): try: os.remove(filePath) except: - print("Error while deleting file : ", filePath) + logger.error("Error while deleting file : ", filePath) def log_control(): outfilename = ".\\output.log" @@ -68,7 +80,7 @@ def run_command(): f.write("\n") #Put stuff in the interpreter - print(f"Running {args.run} on {line}") + logger.info(f"Running {args.run} on {line}") f.write(f"Running psexec \\\\{line} {args.run}") f.write("\n") f.close() @@ -98,11 +110,11 @@ def push_copies(): f.write("\n") f.close() #Put stuff in the interpreter - print(f"Sending {args.copy} to {final_destination}") + logger.info(f"Sending {args.copy} to {final_destination}") #Setup, then run the commands, dumping contents to its own logfile final_command = f'xcopy "{args.copy}" "{final_destination}" /Y /E /H /C /I ^>^> .\\logs\\{line}.log' - print(final_command) + logger.info(final_command) subprocess.Popen(f"start cmd.exe /k {final_command}", shell=True) print("--------------------------------------------") line = fp.readline() @@ -114,13 +126,13 @@ def push_copies(): if os.path.isfile(args.targets) == False: print() - print("File does not exist: " + args.targets) - sys.exit() + logger.error("File does not exist: " + args.targets) + sys.exit(1) if args.copy is None and args.run is None: print() - print("Please specify copy, run, or both.") - sys.exit() + logger.error("Please specify copy, run, or both.") + sys.exit(1) #Zhu-Li, do the thing. if args.copy is not None and args.run is not None: @@ -130,18 +142,18 @@ def push_copies(): run_command() input("Once all commands have ran, press Enter to compile logs and close...") log_control() - sys.exit() + sys.exit(0) if args.copy is not None: log_cleaning() push_copies() input("Once all copies have been pushed (All windows closed), press Enter to compile logs and close...") log_control() - sys.exit() + sys.exit(0) if args.run is not None: log_cleaning() run_command() input("Once all commands have ran (All windows closed), press Enter to compile logs and close...") log_control() - sys.exit() + sys.exit(0) From 950f28bbebea1fdeb5dc8817a8d0857008503cc1 Mon Sep 17 00:00:00 2001 From: kailando <94944679+kailando@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:24:41 -0500 Subject: [PATCH 3/5] Patch 4 (#2) * Adding PowerApps Notes * Notes and new util * Added jigglers * User profile tools * ogv > ft for remote command * New stuff around teams and gpos * Use access.log from logon script instead-reliable * Reorganized, dotsource boilerplate, bug fixes * Reorganized, dotsource boilerplate, bug fixes * Rename for verb matching * Fixed registry pointing * Changed erroraction * Added AD Queries * Added notes * Extra tools * Beginning PIM work * New utility for multi domain pass change w keepass * Migrated tool to dot source * logon to checking * Added domain trust repair note * Notes on AD restoration * Added wp plugin command * Citrix ps entry * Add logging and exit codes Joins.py --------- Co-authored-by: Xattle --- SmallUtilities/Joins.py | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/SmallUtilities/Joins.py b/SmallUtilities/Joins.py index 7db2f9f..4e693da 100644 --- a/SmallUtilities/Joins.py +++ b/SmallUtilities/Joins.py @@ -2,6 +2,19 @@ import argparse import textwrap import os.path +import logging + +#Make logger and do stuff +logger=logging.getLogger(__name__) +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +del handler #Handle command line args parser = argparse.ArgumentParser( @@ -28,19 +41,25 @@ args = parser.parse_args() if args.filea == '': - sys.exit('File A must be specified') + logger.critical('File A must be specified') + sys.exit(1) if args.fileb == '': - sys.exit('File B must be specified') + logger.critical('File B must be specified') + sys.exit(1) if not os.path.isfile(args.filea): - sys.exit('Cannot find file {}!'.format(args.filea)) + logger.critical('Cannot find file {}!'.format(args.filea)) + sys.exit(1) if not os.path.isfile(args.fileb): - sys.exit('Cannot find file {}!'.format(args.fileb)) + logger.critical('Cannot find file {}!'.format(args.fileb)) + sys.exit(1) if args.fileacolumn == '': - sys.exit('File A column header to join must be specified.') + logger.critical('File A column header to join must be specified.') + sys.exit(1) if args.filebcolumn == '': - sys.exit('File B column header to join must be specified.') + logger.critical('File B column header to join must be specified.') + sys.exit(1) def trim_all_columns(df): """ @@ -56,7 +75,8 @@ def runjoin(filea, fileatype, fileacolumn, fileb, filebtype, filebcolumn, jointy elif fileatype.lower() == 'excel': table_a = pd.read_excel(filea, dtype=str) else: - sys.exit("Fileatype must be set to listed parameter.") + logger.critical("Fileatype must be set to listed parameter.") + sys.exit(1) if filebtype.lower() == 'csv': @@ -64,7 +84,8 @@ def runjoin(filea, fileatype, fileacolumn, fileb, filebtype, filebcolumn, jointy elif filebtype.lower() == 'excel': table_b = pd.read_excel(fileb, dtype=str) else: - sys.exit("Filebtype must be set to listed parameter.") + logger.critical("Filebtype must be set to listed parameter.") + sys.exit(1) trim_all_columns(table_a) trim_all_columns(table_b) From 99b71582924136374c9cb8dec911a379f4aea91b Mon Sep 17 00:00:00 2001 From: kailando <94944679+kailando@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:24:55 -0500 Subject: [PATCH 4/5] Patch 5 (#1) * Adding PowerApps Notes * Notes and new util * Added jigglers * User profile tools * ogv > ft for remote command * New stuff around teams and gpos * Use access.log from logon script instead-reliable * Reorganized, dotsource boilerplate, bug fixes * Reorganized, dotsource boilerplate, bug fixes * Rename for verb matching * Fixed registry pointing * Changed erroraction * Added AD Queries * Added notes * Extra tools * Beginning PIM work * New utility for multi domain pass change w keepass * Migrated tool to dot source * logon to checking * Added domain trust repair note * Notes on AD restoration * Added wp plugin command * Citrix ps entry * Add logging to SFTPull.py --------- Co-authored-by: Xattle --- SmallUtilities/SFTPull.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/SmallUtilities/SFTPull.py b/SmallUtilities/SFTPull.py index dfed97f..a4ec428 100644 --- a/SmallUtilities/SFTPull.py +++ b/SmallUtilities/SFTPull.py @@ -4,6 +4,19 @@ import shutil import ntpath import os +import logger + +#Make logger and do stuff +logger=logging.getLogger(__name__) +logger.setLevel(logging.INFO) +formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +handler = logging.StreamHandler(sys.stderr) +handler.setFormatter(formatter) +logger.addHandler(handler) +del handler #Handle command line args parser = argparse.ArgumentParser( @@ -31,12 +44,12 @@ def path_leaf(path): head, tail = ntpath.split(path) return tail or ntpath.basename(head) -print("Connecting to {} as {}".format(args.server, args.username)) +logger.info("Connecting to {} as {}".format(args.server, args.username)) con_info = {'host':args.server,'username':args.username,'password':args.password,'port':args.port} with pysftp.Connection(**con_info) as sftp: - print("Copying {} locally.".format(args.serverlocation)) + logger.info("Copying {} locally.".format(args.serverlocation)) sftp.get(args.serverlocation) -print("Relocating local {} to {}".format(path_leaf(args.serverlocation),os.path.realpath(args.savelocation))) +logger.info("Relocating local {} to {}".format(path_leaf(args.serverlocation),os.path.realpath(args.savelocation))) shutil.move(path_leaf(args.serverlocation), os.path.realpath(args.savelocation)) -print("Operations finished.") +logger.info("Operations finished.") From 797ab0840087daabfb016fa003a5b22a4f44fa6d Mon Sep 17 00:00:00 2001 From: kailando <94944679+kailando@users.noreply.github.com> Date: Thu, 21 Aug 2025 13:25:35 -0500 Subject: [PATCH 5/5] Patch 1 (#5) * Adding PowerApps Notes * Notes and new util * Added jigglers * User profile tools * ogv > ft for remote command * New stuff around teams and gpos * Use access.log from logon script instead-reliable * Reorganized, dotsource boilerplate, bug fixes * Reorganized, dotsource boilerplate, bug fixes * Rename for verb matching * Fixed registry pointing * Changed erroraction * Added AD Queries * Added notes * Extra tools * Beginning PIM work * New utility for multi domain pass change w keepass * Migrated tool to dot source * logon to checking * Added domain trust repair note * Notes on AD restoration * Added wp plugin command * Citrix ps entry * Update hrefStripper.py --- Exits better Make sys.exit work and set error codes. Also, more error handling --------- Co-authored-by: Xattle --- SmallUtilities/hrefStripper.py | 61 +++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/SmallUtilities/hrefStripper.py b/SmallUtilities/hrefStripper.py index 3071c53..1bd8f79 100644 --- a/SmallUtilities/hrefStripper.py +++ b/SmallUtilities/hrefStripper.py @@ -10,38 +10,61 @@ parser.add_argument("-o", "--outfile", help="Set the csv output here.") args = parser.parse_args() +# Helper for exiting with error codes +def get_error_code(exc): + # OSError and its subclasses have errno attribute on POSIX + if hasattr(exc, 'errno') and exc.errno is not None: + return exc.errno + # On Windows, sometimes only winerror is set + if hasattr(exc, 'winerror') and exc.winerror is not None: + return exc.winerror + # Fallback: no meaningful error code found + return 1 + nl = "\n" #Error handling if ".csv" not in args.infile: print("Infile must be of type .csv") - sys.exit + sys.exit(1) if ".csv" not in args.outfile: print("Outfile must be of type .csv") - sys.exit + sys.exit(1) if os.path.isfile(args.infile) == False: print("Infile does not exist.") - sys.exit + sys.exit(get_error_code(FileNotFoundError())) # File not found error exit code if os.path.isfile(args.outfile) == True: print("Outfile exists! Overwriting File") input("Press ENTER to confirm overwrite...") os.remove(args.outfile) -f = open(f"{args.outfile}", "a") - -with open(args.infile, 'r') as read_obj: - csv_reader = csv.reader(read_obj, delimiter="#", quotechar="|") - for row in csv_reader: - #Magic happens here - output_data = row - row_hrefs = "" - soup = BeautifulSoup(row[4]) - for link in soup.findAll("a"): - this_href = link.get("href") - row_hrefs = (f"{row_hrefs} {this_href}") - output_data.append(row_hrefs) - print(row_hrefs) - f.write("#####".join(output_data)) - f.write(nl) +try: + f = open(f"{args.outfile}", "a") +except OSError as e: + print(f"Error while opening outfile: {e}") + sys.exit(get_error_code(e)) + +try: + read_obj = open(args.infile, 'r') +except OSError as e: + print(f"Error while opening infile: {e}") + sys.exit(get_error_code(e)) + +csv_reader = csv.reader(read_obj, delimiter="#", quotechar="|") +for row in csv_reader: + #Magic happens here + output_data = row + row_hrefs = "" + soup = BeautifulSoup(row[4]) + for link in soup.findAll("a"): + this_href = link.get("href") + row_hrefs = (f"{row_hrefs} {this_href}") + output_data.append(row_hrefs) + print(row_hrefs) + f.write("#####".join(output_data)) + f.write(nl) + +f.close() +read_obj.close()