SysAdminTools.psm1
<#
.SYNOPSIS Adds a user that is currently in AD to a computer's "Remote Desktop Users" group. .DESCRIPTION Adds a user that is currently in AD to a computer's "Remote Desktop Users" group. .EXAMPLE PS C:\WINDOWS\system32> Add-RemoteDesktopUser -ComputerName pancake-3 -SamAccountName mrpig ComputerName SamAccountName UserAdded ------------ -------------- --------- pancake-3 mrpig True This examples adds the user "mrpig" to the computer pancake-3. .INPUTS [String] ComputerName [String] SamAccountName .OUTPUTS [PSCUSTOMOBJECT] .NOTES Requires admin. #> function Add-RemoteDesktopUser{ [CmdletBinding()] param( [Parameter(Mandatory,Position=0, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [string]$ComputerName, [Parameter(Mandatory,Position=1, ValueFromPipelineByPropertyName)] [string]$SamAccountName, [string]$Domain = $ENV:USERDOMAIN ) try{ if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet){ #need to change error action perference to make any errors terminating since we are not using native powershell functions $ErrorActionPreference = "Stop" [ADSI]$Account = "WinNT://$Domain/$SamAccountName,User" [ADSI]$Group = "WinNT://$ComputerName/Remote Desktop Users,Group" $Group.Add($Account.Path) [PSCustomObject]@{ ComputerName = $ComputerName SamAccountName = $SamAccountName UserDomain = $Domain UserAdded = $true } $ErrorActionPreference = "Continue" } else{ Write-Error -Message "$ComputerName is offline or unreachable." } } catch{ $ErrorActionPreference = "Continue" $PSCmdlet.WriteError($_) } } <# .SYNOPSIS This functions get the current state of both internal and external (UPS) batteries. .DESCRIPTION This functions get the current state of both internal and external (UPS) batteries. Including their charging state, charge remaining, estimated run time, etc. .EXAMPLE PS C:\> Get-BatteryStatus ComputerName Name Charge (%) Run Time Battery Status Status ------------ ---- ---------- -------- -------------- ------ DESKTOP-RFR3S01 GX1500U [XXXXXXXXXXXXXXXXXXXX] 100% 55 Connected_To_AC OK This example gets the local computer's battery status. The view built for this function shows the charge as bar graph (colored in the console). .INPUTS System.String -ComputerName .OUTPUTS SysAdminTools.BatteryStatus .NOTES This function uses WsMan by default and Dcom protocol if that fails. #> function Get-BatteryStatus{ [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias("CN","Name","IPAddress")] [string[]]$ComputerName = $ENV:COMPUTERNAME ) Begin{ enum Availability { Other = 1 Unknown = 2 Running_FullPower = 3 Warning = 4 In_Test = 5 Not_Applicable = 6 Power_Off = 7 Offline = 8 Off_Duty = 9 Degraded = 10 Not_Installed = 11 Install_Error = 12 Power_Save_Unknown = 13 Power_Save_Low_Power_Mode = 14 Power_Save_StandBy = 15 Power_Cycle = 16 Power_Save_Warning = 17 Paused = 18 Not_Ready = 19 Not_Configured = 20 Quiesced = 21 } enum BatteryStatus { Other_Discharging = 1 Connected_To_AC = 2 Fully_Charged = 3 Low = 4 Critical = 5 Charging = 6 Charing_High = 7 Charging_Low = 8 Charging_Critical = 9 Partially_Charged = 11 } enum Chemistry { Other = 1 Unknown = 2 Lead_Acid = 3 Nickel_Cadmium = 4 Nickel_Metal_Hydride = 5 Lithium_ion = 6 Zinc_air = 7 Lithium_Polymer = 8 } enum PowerManagementCapabilities { Unknown = 0 Not_Supported = 1 Disabled = 2 Enabled = 3 Power_Saving_Modes_Entered_Automatically = 4 Power_State_Settable = 5 Power_Cycling_Supported = 6 Timed_Power_On_Supported = 7 } } #begin Process{ foreach ($computer in $ComputerName){ if (Test-Connection -ComputerName $computer -Count 1 -Quiet){ Try{ $CimSession = New-CimSession -ComputerName $computer -OperationTimeoutSec 1 -ErrorAction Stop } catch{ try{ Write-Information "Unable to connect to $computer with Wsman, using DCOM protocl instead" -Tags 'Process' $CimSession = New-CimSession -ComputerName $computer -SessionOption (New-CimSessionOption -Protocol Dcom) -OperationTimeoutSec 1 -ErrorAction Stop } catch{ Write-Error "Unable to connect to $computer with Wsman or Dcom protocols" continue } } try{ $Batteries = Get-CimInstance -CimSession $CimSession -ClassName Win32_Battery foreach ($battery in $Batteries){ [PSCustomObject]@{ PSTypeName = "SysAdminTools.BatteryStatus" ComputerName = $computer Name = $battery.Name DesignVoltage = $battery.DesignVoltage EstimatedChargeRemaining = $battery.EstimatedChargeRemaining EstimatedRunTime = $battery.EstimatedRunTime Availability = [Availability]($battery.Availability) BatteryStatus = [BatteryStatus]($battery.BatteryStatus) Chemistry = [Chemistry]($battery.Chemistry) Status = $battery.Status DeviceID = $battery.DeviceID } } $CimSession | Remove-CimSession } catch{ if ($CimSession){ $CimSession | Remove-CimSession } $PSCmdlet.WriteError($_) } #catch } #try } #foreach } #process } <# .SYNOPSIS Gets the default printer of a remote machine. .DESCRIPTION Gets the default printer of a remote machine of a specfifc user, including Shared Server Printers. .PARAMETER ComputerName Use this paramter to specify the computer(s) you want to run the command aganist using its name or IPAddress. .PARAMETER SamAccountName This paramter allows you to only grab the default printers of the specifed user(s). This value is evaluated against the leaf of the localpath from Win32_UserProfile class .PARAMETER Quiet This parameter prevents errors being generated if the computer is unreachable .EXAMPLE PS C:\> Get-DefaultPrinter -ComputerName Client01v PrintServer PrinterName UserName ComputerName ----------- ----------- -------- ------------ Local OneNote pwsh.cc Client01v This example gets the default printer of the only logged in user on Client01v. .EXAMPLE PS C:\>Get-DefaultPrinter -ComputerName Client01v,dc01v PrintServer PrinterName UserName ComputerName ----------- ----------- -------- ------------ Local OneNote pwsh.cc Client01v Local Microsoft Print to PDF Administrator dc01v PS C:\> Get-DefaultPrinter -ComputerName Client01v,dc01v -SamAccountName pwsh.cc PrintServer PrinterName UserName ComputerName ----------- ----------- -------- ------------ Local OneNote pwsh.cc Client01v In this example it shows how the SamAccountName parameter works by only grabbing default users specified. Only pwsh.cc was returned by the second command even though the current user Administrator had a default printer on dc01v. .EXAMPLE PS C:\>Test-Connection -ComputerName client01v -Count 1 -Quiet False PS C:\Users\Administrator> Get-DefaultPrinter -ComputerName Client01v,dc01v -Quiet PrintServer PrinterName UserName ComputerName ----------- ----------- -------- ------------ Local Microsoft Print to PDF Administrator dc01v In this example we can see how the quiet paramter works, by omitting errors of computers that are unreachable such as Client01v. .INPUTS String .OUTPUTS PsCustomObject SysAdminTool.DefaultPrinter .NOTES Uses WsMan Protocol as default and fallsback to DCOM. Grabs default printer from registry using StdRegProv wmi class in the root\default namespace #> function Get-DefaultPrinter{ [cmdletbinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias("CN","Name","MachineName","IPAddress")] [string[]]$ComputerName = $ENV:COMPUTERNAME, [Parameter(ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias("UserName")] [string[]]$SamAccountName, [switch]$Quiet ) Begin{ #Keys to use to reference each regsitry hive, only need HKEY_Users, the other are left for future reference #$HKEY_CLASSES_ROOT = 2147483648 #$HKEY_CURRENT_USER = 2147483649 #$HKEY_LOCAL_MACHINE = 2147483650 $HKEY_USERS = 2147483651 #$HKEY_CURRENT_CONFIG = [Convert]::ToUInt32(2147483653) } Process{ foreach ($computer in $ComputerName){ if (Test-Connection -ComputerName $Computer -Quiet -Count 1){ try{ Write-Information "Test connection to computer $computer successful, creating Cim Session and grabbing win32_UserProfile" -Tags "Process" try{ $session = New-CimSession -ComputerName $computer -OperationTimeoutSec 1 -ErrorAction Stop } catch{ Write-Information "Unable to create Cim Session using WsMan, creating fallback session using DCOM" $CimSessionOption = New-CimSessionOption -Protocol Dcom $session = New-CimSession -ComputerName $computer -SessionOption $CimSessionOption -OperationTimeoutSec 1 } $AllUserProfiles = Get-CimInstance -ClassName Win32_UserProfile -CimSession $session -Filter "SPECIAL=$false" $RemoteRegistry = Get-CimClass -Namespace "root\default" -ClassName StdRegProv -CimSession $session $currentUsersReg = ($RemoteRegistry | Invoke-CimMethod -Name "EnumKey" -Arguments @{hDefKey=$HKEY_USERS;sSubKeyName = ""} -CimSession $session).sNames $currentUsers = $AllUserProfiles | where SID -in $currentUsersReg foreach ($user in $currentUsers){ $SID = $user.SID $UserName = Split-Path $user.LocalPath -Leaf if ($PSBoundParameters.ContainsKey("SamAccountName") -and ($UserName -notin $SamAccountName)){ Write-Information "SamAccountName parameter was used, and the user $UserName was not found as a current user on $computer" -Tags "Process" continue } Write-Information "Attempting to grab default printer for user $UserName" -Tags "Process" $Printer = ($RemoteRegistry | Invoke-CimMethod -MethodName GetStringValue -Arguments @{hDefKey=$HKEY_USERS;sSubKeyName = "$SID\Software\Microsoft\Windows NT\CurrentVersion\Windows";sValueName = "Device"} -CimSession $session).sValue if ($Printer){ Write-Information "A default printer was found for user $UserName, creating output object" -Tags "Process" if ($Printer.StartsWith('\\')){ $PrinterPath = $printer.Split(",")[0] $ServerandPrinter = $PrinterPath.Split('\',[System.StringSplitOptions]::RemoveEmptyEntries) [PSCustomObject]@{ PSTypeName = "SysAdminTools.DefaultPrinter" PrintServer = $ServerandPrinter[0] PrinterName = $ServerandPrinter[1] UserName = $UserName ComputerName = $computer } } else{ $PrinterPath = $printer.Split(",")[0] [PSCustomObject]@{ PSTypeName = "SysAdminTools.DefaultPrinter" PrintServer = "Local" PrinterName = $PrinterPath UserName = $UserName ComputerName = $computer } } } #if else{ Write-Information "No default printer was found for user $Username" -Tags "Process" } } #foreach user $session | Remove-CimSession } #try catch [System.Runtime.InteropServices.COMException]{ Write-Warning "WMI query failed on $computer. Ensure 'Windows Management Instrumentation (WMI-In)' firewall rule is enabled." $PSCmdlet.WriteError($_) } catch{ Write-Warning "An uncaught execption has occurred please open an issue at https://github.com/MrPig91/SysAdminTools/issues" $PSCmdlet.WriteError($_) } } #if connection else{ if ($Quiet){ Write-Information "The quiet switch was used, skipping the connection fail error for $computer" -Tags "Process" } else{ $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Net.NetworkInformation.PingException]::new("$computer is unreachable"), 'TestConnectionException', [System.Management.Automation.ErrorCategory]::ConnectionError, $computer ) $PSCmdlet.WriteError($ErrorRecord) } } } #foreach computer } #Process } <# .SYNOPSIS This function gets the current user sesions on a remote or local computer. .DESCRIPTION This function uses quser.exe to get the current user sessions from a remote or local computer. .PARAMETER ComputerName Use this paramter to specify the computer you want to run the command aganist using its name or IPAddress. .EXAMPLE PS C:\> Get-LoggedInUser ComputerName UserName ID SessionType State ScreenLocked IdleTime ------------ -------- -- ----------- ----- ------------ -------- DESKTOP-D7FU4K5 pwsh.cc 1 DirectLogon Active False 0 This examples gets the logged in users of the local computer. .EXAMPLE Get-LoggedInUser -ComputerName $env:COMPUTERNAME,dc01v ComputerName UserName ID SessionType State ScreenLocked IdleTime ------------ -------- -- ----------- ----- ------------ -------- DESKTOP-D7FU4K5 pwsh.cc 1 DirectLogon Active False 0 dc01v administrator 1 DirectLogon Active False 0 This example gets the currently logged on users for the local computer and a remote computer called dc01v. .INPUTS System.String You can pipe a string that contains the computer name. .OUTPUTS AdminTools.LoggedInuser Outputs a custom powershell object .NOTES Requires Admin #> Function Get-LoggedInUser () { [CmdletBinding()] Param ( [Parameter(ValueFromPipelineByPropertyName, ValueFromPipeline)] [Alias("CN","Name","MachineName")] [string[]]$ComputerName = $ENV:ComputerName ) PROCESS { foreach ($computer in $ComputerName){ try{ Write-Information "Testing connection to $computer" -Tags 'Process' if (Test-Connection -ComputerName $computer -Count 1 -Quiet){ $Users = quser.exe /server:$computer 2>$null | select -Skip 1 if (!$?){ Write-Information "Error with quser.exe" -Tags 'Process' if ($Global:Error[0].Exception.Message -eq ""){ throw $Global:Error[1] } elseif ($Global:Error[0].Exception.Message -like "No User exists*"){ Write-Warning "No users logged into $computer" } else{ throw $Global:Error[0] } } $LoggedOnUsers = foreach ($user in $users){ [PSCustomObject]@{ PSTypeName = "AdminTools.LoggedInUser" ComputerName = $computer UserName = (-join $user[1 .. 20]).Trim() SessionName = (-join $user[23 .. 37]).Trim() SessionId = [int](-join $user[38 .. 44]) State = (-join $user[46 .. 53]).Trim() IdleTime = (-join $user[54 .. 63]).Trim() LogonTime = [datetime](-join $user[65 .. ($user.Length - 1)]) LockScreenPresent = $false LockScreenTimer = (New-TimeSpan) SessionType = "TBD" } } try { Write-Information "Using WinRM and CIM to grab LogonUI process" -Tags 'Process' $LogonUI = Get-CimInstance -ClassName win32_process -Filter "Name = 'LogonUI.exe'" -ComputerName $Computer -Property SessionId,Name,CreationDate -OperationTimeoutSec 1 -ErrorAction Stop } catch{ Write-Information "WinRM is not configured for $computer, using Dcom and WMI to grab LogonUI process" -Tags 'Process' $LogonUI = Get-WmiObject -Class win32_process -ComputerName $computer -Filter "Name = 'LogonUI.exe'" -Property SessionId,Name,CreationDate -ErrorAction Stop | select name,SessionId,@{n="Time";e={[DateTime]::Now - $_.ConvertToDateTime($_.CreationDate)}} } foreach ($user in $LoggedOnUsers){ if ($LogonUI.SessionId -contains $user.SessionId){ $user.LockScreenPresent = $True $user.LockScreenTimer = ($LogonUI | where SessionId -eq $user.SessionId).Time } if ($user.State -eq "Disc"){ $user.State = "Disconnected" } $user.SessionType = switch -wildcard ($user.SessionName){ "Console" {"DirectLogon"; Break} "" {"Unkown"; Break} "rdp*" {"RDP"; Break} default {""} } if ($user.IdleTime -ne "None" -and $user.IdleTime -ne "."){ if ($user.IdleTime -Like "*+*"){ $user.IdleTime = New-TimeSpan -Days $user.IdleTime.Split('+')[0] -Hours $user.IdleTime.Split('+')[1].split(":")[0] -Minutes $user.IdleTime.Split('+')[1].split(":")[1] } elseif($user.IdleTime -like "*:*"){ $user.idleTime = New-TimeSpan -Hours $user.IdleTime.Split(":")[0] -Minutes $user.IdleTime.Split(":")[1] } else{ $user.idleTime = New-TimeSpan -Minutes $user.IdleTime } } else{ $user.idleTime = New-TimeSpan } $user | Add-Member -Name LogOffUser -Value {logoff $this.SessionId /server:$($this.ComputerName)} -MemberType ScriptMethod $user | Add-Member -MemberType AliasProperty -Name ScreenLocked -Value LockScreenPresent Write-Information "Outputting user object $($user.UserName)" -Tags 'Process' $user } #foreach } #if ping else{ $ErrorRecord = [System.Management.Automation.ErrorRecord]::new( [System.Net.NetworkInformation.PingException]::new("$computer is unreachable"), 'TestConnectionException', [System.Management.Automation.ErrorCategory]::ConnectionError, $computer ) $PSCmdlet.WriteError($ErrorRecord) } } #try catch [System.Management.Automation.RemoteException]{ if ($_.Exception.Message -like "*The RPC server is unavailable*"){ Write-Warning "quser.exe failed on $comptuer, Ensure 'Netlogon Service (NP-In)' firewall rule is enabled" $PSCmdlet.WriteError($_) } else{ $PSCmdlet.WriteError($_) } } catch [System.Runtime.InteropServices.COMException]{ Write-Warning "WMI query failed on $computer. Ensure 'Windows Management Instrumentation (WMI-In)' firewall rule is enabled." $PSCmdlet.WriteError($_) } catch{ Write-Information "Unexpected error occurred with $computer" $PSCmdlet.WriteError($_) } } #foreach } #process } <# .SYNOPSIS This will grab the serial number, monitor name, and year of manufacture of all monitors connected to a computer. .PARAMETER ComputerName Use this paramter to specify the computer(s) you want to run the command aganist using its name or IPAddress. .DESCRIPTION This functions grabs the serial number, monitor name, and year of manufacture of all monitors connected to a computer. .EXAMPLE PS C:\> Get-MonitorInfo ComputerName MonitorName SerialNumber YearOfManufacture ------------ ----------- ------------ ----------------- DESKTOP-RFR3S01 Acer K272HUL T0SAA0014200 2014 DESKTOP-RFR3S01 VX2457 UG01842A1649 2018 This example grabs the monitors connected to the local computer. .EXAMPLE PS C:\> Get-ComputerMonitor Client01v,Client02v ComputerName MonitorName SerialNumber YearOfManufacture ------------ ----------- ------------ ----------------- Client01v HP HC240 XXXXXXXXXX 2017 Client01v HP HC240 XXXXXXXXXX 2017 Client02v HP E243i XXXXXXXXXX 2018 Client02v HP E243i XXXXXXXXXX 2018 This example uses the ComputerName parameter, but it does so positionally which is why it is not written out. It grabs the info for all monitors connected to Client01v and Client02v. .INPUTS None .OUTPUTS PsCustomObject .NOTES Does not grab built-in monitor info. #> function Get-MonitorInfo{ [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [Alias("CN","Name","IPAddress")] [ValidateNotNullOrEmpty()] [string[]]$ComputerName = $Env:COMPUTERNAME, [Parameter()] [ValidateSet("Wsman","Dcom")] $Protocol = "Wsman" ) Begin{ $options = New-CimSessionOption -Protocol $Protocol } Process{ foreach ($computer in $ComputerName){ try{ Write-Information -MessageData "Creating new cim session for $computer with a $protocol connection" -Tags "Process" $Session = New-CimSession -ComputerName $computer -OperationTimeoutSec 1 -SessionOption $options -ErrorAction Stop Write-Information -MessageData "Calling WMIMonitorID Class to grab monitor info for computer $computer" -Tags "Process" $monitors = Get-CimInstance -ClassName WmiMonitorID -Namespace root\wmi -CimSession $Session | Where-Object UserFriendlyNameLength -NE 0 foreach ($monitor in $monitors){ $SerialNumber = ($monitor.SerialNumberID -ne 0 | ForEach-Object{[char]$_}) -join "" $MonitorName = ($monitor.UserFriendlyName -ne 0 | ForEach-Object{[char]$_}) -join "" $Object = [PSCustomObject]@{ PSTypeName = "SysAdminTools.Monitor" ComputerName = $computer.ToUpper() MonitorName = $MonitorName SerialNumber = $SerialNumber YearOfManufacture = $monitor.YearOfManufacture } $Object Write-Information -MessageData "Created object for monitor $($object.MonitorName)" -Tags "Process" } #foreach Write-Information -MessageData "Removing $computer cim session" -Tags "Process" Get-CimSession | where computername -eq $computer | Remove-CimSession } catch{ Write-Warning "Unable to grab monitor info for $computer" } } #foreach computer } #Process } function Get-RemoteDesktopUser{ [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [string[]]$ComputerName, [Parameter(ValueFromPipelineByPropertyName)] [string[]]$UserName ) Begin{ } #Begin Process{ foreach ($computer in $ComputerName){ Get-sysLocalGroupMember -ComputerName $computer -GroupName "Remote Desktop Users" } #foreach } #process } <# .SYNOPSIS This function grabs local groups along with their members (if any) from local or remote computers. .DESCRIPTION This function uses Win32_Group and Win32_GroupUser to grab local groups and their members form local and remote computers. .EXAMPLE PS C:\> Get-sysLocalGroupMember -GroupName "Remote Desktop Users","RDS Remote Access Servers" GroupName Member ComputerName --------- ------ ------------ Remote Desktop Users {Everyone, mrpig} DC01 RDS Remote Access Servers DC01 This examples grabs the group and members of 2 groups specified with the GroupName parameter from the local computer. .EXAMPLE PS C:\>Get-sysLocalGroupMember -IncludeGroupsWithMembersOnly GroupName Member --------- ------ Pre-Windows 2000 Compatible Access {Authenticated Users} Windows Authorization Access Group {ENTERPRISE DOMAIN CONTROLLERS} Administrators {Administrator, Enterprise Admins, Domain Admins} Users {INTERACTIVE, Authenticated Users, Domain Users} Guests {Guest, Domain Guests} Remote Desktop Users {Everyone, mrpig} IIS_IUSRS {IUSR} Denied RODC Password Replication Group {krbtgt, Domain Controllers, Schema Admins, Enterprise Admins, Cert Publisher... This example grabs all groups from the local computer if they have any members and ignores the one's with no members. .EXAMPLE PS C:\>Get-sysLocalGroupMember -ComputerName $ENV:COMPUTERNAME,pancake-3 -Protocol Dcom -GroupName "Remote Desktop Users" -OutVariable groups GroupName Member ComputerName --------- ------ ------------ Remote Desktop Users {Everyone, mrpig} DC01 Remote Desktop Users {mrpig, mrpig} pancake-3 PS C:\>$groups[1].Member Name Domain MemberType ---- ------ ---------- mrpig CLEVELAND UserAccount mrpig PANCAKE-3 UserAccount This example grabs members of the "Remote Desktop Users" group from both dc01 and pancake-3 and uses the Dcom protocol since pancake-3 does not have WsMan enabled. It then stores the results into the groups variable. The second command expands the Member property of pancake-3 LocalGroup object to get more info about each group member. .INPUTS Inputs (if any) .OUTPUTS [SysAdminTools.LocalGroupMember] .NOTES GroupName has tab completetion, but it only grabs the local groups from the local computer and not remote ones, but generally they should be the same. #> function Get-sysLocalGroupMember{ [CmdletBinding()] param( [Parameter(ValueFromPipelineByPropertyName,ValueFromPipeline)] [string[]]$ComputerName = $ENV:COMPUTERNAME, [ArgumentCompleter({ param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameter) Get-CimInstance -ClassName Win32_Group -Filter "LocalAccount=True AND Name LIKE `"%$WordToComplete%`"" | where Name -notin $fakeBoundParameter.GroupName | foreach { $ToolTip = $_ | Format-List Name,Description,Caption | Out-String [System.Management.Automation.CompletionResult]::new("`"$($_.Name)`"","$($_.Name)","ParameterValue",$ToolTip) } })] [Parameter(ValueFromPipeline)] [string[]]$GroupName, [switch]$IncludeGroupsWithMembersOnly, [Parameter()] [ValidateSet("WsMan", "Dcom")] [string]$Protocol = "WsMan" ) Begin{ if ($PSBoundParameters.ContainsKey("GroupName")){ $GroupNameFilter = "AND (Name=`"$($GroupName[0])`"" foreach ($name in ($GroupName | select -Skip 1)){ $GroupNameFilter += " OR Name=`"$name`"" } $GroupNameFilter += ")" } } #Begin Process{ foreach ($computer in $ComputerName){ try{ $Session = New-CimConnection -ComputerName $computer -Protocol $Protocol -ErrorAction Stop $Groups = Get-CimInstance -CimSession $Session -ClassName Win32_Group -Filter "LocalAccount=True $GroupNameFilter" foreach ($group in $Groups){ $GroupComponent = Get-CimInstance -CimSession $Session -ClassName Win32_GroupUser -Filter "GroupComponent=""Win32_Group.Domain='$computer',Name='$($group.Name)'""" if ($IncludeGroupsWithMembersOnly){ if ($GroupComponent -eq $null){ continue } } $Members = foreach ($member in $GroupComponent){ [PSCustomObject]@{ PSTypeName = "SysAdminTools.LocalGroupMember" Name = $member.PartComponent.Name Domain = $member.PartComponent.Domain MemberType = $member.PartComponent.cimclass.cimclassname.split('_')[1] } } [PSCustomObject]@{ PSTypeName = "SysAdminTools.LocalGroup" ComputerName = $computer GroupName = $group.Name Member = $Members Description = $group.Description Domain = $group.Domain Caption = $group.Caption SID = $group.SID SIDType = $group.SIDType LocalAccount = $group.LocalAccount Status = $group.Status } } $Session | Remove-CimSession } catch{ if ($Session){ $Session | Remove-CimSession } $PSCmdlet.WriteError($_) } } #foreach computer } #Process } <# .SYNOPSIS This function will remove a user from the "Remote Desktop Users" group from a remote machine. .DESCRIPTION This function will remove a user from the "Remote Desktop Users" group from a remote machine. .EXAMPLE PS C:\> Remove-RemoteDesktopUser -ComputerName mrpig -SamAccountName mrpig ComputerName SamAccountName UserRemoved ------------ -------------- ----------- pancake-3 mrpig True In this example the user account mrpig is removed from the "Remote Desktop Users" group on the computer mrpig. .EXAMPLE PS C:\>Get-sysLocalGroupMember -ComputerName pancake-3 -GroupName "Remote Desktop Users" -Protocol Dcom GroupName Member ComputerName --------- ------ ------------ Remote Desktop Users {mrpig, mrpig} pancake-3 PS C:\> Remove-RemoteDesktopUser -ComputerName pancake-3 -SamAccountName mrpig -Protocol Dcom ComputerName SamAccountName UserDomain UserRemoved ------------ -------------- ---------- ----------- pancake-3 mrpig CLEVELAND True Remove-RemoteDesktopUser -ComputerName pancake-3 -SamAccountName mrpig -Protocol Dcom -Domain pancake-3 ComputerName SamAccountName UserDomain UserRemoved ------------ -------------- ---------- ----------- pancake-3 mrpig pancake-3 True This example should show you full functionality of the command. The first command grabs the current users of the "Remote Desktop Users" group on pancake-3. We can see that there are 2 mrpig accounts in that group, we could expand the Member property to see one is domain mrpig account and the other is local mrpig account. The second command removes the domain mrpig account, it does this because the default value of the domain paramter is the current user's domain. The third command specifies the pancake-3 as the domain to target the pancake-3\mrpig account to remove. .INPUTS [String] .OUTPUTS [PSCustomObject] .NOTES Requires Admin. #> function Remove-RemoteDesktopUser{ [CmdletBinding()] param( [Parameter(Mandatory)] [string]$ComputerName, [Parameter(Mandatory)] [string]$SamAccountName, [string]$Domain = $ENV:USERDOMAIN, [ValidateSet("WsMan","Dcom")] [string]$Protocol ) try{ if (Test-Connection -ComputerName $ComputerName -Count 1 -Quiet){ $Options = New-CimSessionOption -Protocol Dcom $Sesssion = New-CimSession -ComputerName $ComputerName -OperationTimeoutSec 1 -SessionOption $options -ErrorAction Stop $Users = Get-sysLocalGroupMember -ComputerName $ComputerName -GroupName "Remote Desktop Users" -Protocol $Protocol $UserFound = $Users | where {$_.Member.Name -eq $SamAccountName -and $_.Member.Domain -eq $Domain} if ($UserFound){ $ErrorActionPreference = "Stop" [ADSI]$Account = "WinNT://$Domain/$SamAccountName,User" [ADSI]$Group = "WinNT://$ComputerName/Remote Desktop Users,Group" $Group.Remove($Account.Path) [PSCustomObject]@{ ComputerName = $ComputerName SamAccountName = $SamAccountName UserDomain = $Domain UserRemoved = $true } $ErrorActionPreference = "Continue" } else{ Write-Error -Message "$SamAccountName is not a member of the Remote Desktop Users group on $ComputerName. Try using 'Get-sysLocalGroupMember -ComputerName $ComputerName -GroupName `"Remote Desktop Users`"' to find the current members of that group."` -ErrorAction Stop } $Sesssion | Remove-CimSession } else{ Write-Error -Message "$ComputerName is offline or unreachable. Possibly try grabbing its IP address by using Get-ComputerIP -ComputerName $ComputerName" } } catch{ if ($Sesssion){ $Sesssion | Remove-CimSession } $ErrorActionPreference = "Continue" $PSCmdlet.WriteError($_) } } <# .SYNOPSIS This function will set the Powershell prompt with some predefined functions like the CPU/Mem, Random Command, etc. .DESCRIPTION This function uses the "function Prompt {}" function to set the powershell prompt with a new function. You can add a prefix to your main prompt display, change the color of the foreground or background. Save this command to your powershell profile to have your custom prompt ready to go every time you open up powershell. .PARAMETER Name This paramter sets the prompt to a predefined funtion like showing the last commands execution time or a random fact. .PARAMETER Prefix This paramter prefixes the prompt with a prefined function that is enclosed with [] and can be added together. The order you place them when you run the command is the order they will appear on the prompt. .PARAMETER ForegroundColor This is the color the foreground text will be. You can set this color by calling $prompt_FGColor at anytime after calling the function for the first time. .PARAMETER BackgroundColor This is the color that the background text will be. You can set this color by calling $prompt_BGColor at anytime after calling the function for the first time. .PARAMETER Scriptblock This paramter allows you to set your custom scriptblock to run every time the prompt function is called. .EXAMPLE PS C:\> Set-Prompt -Name CPU_Memory -Prefix Admin -ForegroundColor Red -BackGroundColor Blue [Non-Admin]CPU: 100% | Mem: 37%:\> Tthis function sets the prompt to display the current CPU and memory usage. Also lets you know if the user is an admin or not. Also changed the color of the prompt. .EXAMPLE PS C:\>Set-Prompt -Name Measure_Command -Prefix Error_Count,Admin -ForegroundColor Red -BackGroundColor Green [7][Non-Admin]0 milliseconds:\> 1.. 100 | foreach {Get-CimInstance -ClassName Win32_Process | where name -eq "Explorer.exe"} | Out-Null [7][Non-Admin]4.18 seconds:\> 1.. 100 | foreach {Get-CimInstance -ClassName Win32_Process -Filter "Name='Explorer.exe'"} | Out-Null [7][Non-Admin]3.05 seconds:\> This example sets the prompt to show the amount of time it took the last command to run and displays the number of errors as a prefix. Here you can see that filtering with the Filter paramter of Get-CimInstance is faster than filtering on the left. Always filter left when possible. This particular prompt is useful when creating new functions and trying to optmize them. .EXAMPLE PS C:\>Set-Prompt -ScriptBlock {"$((Get-NetIPAddress -AddressState Preferred -AddressFamily IPv4 -PrefixOrigin Dhcp).IPAddress):\>"} 10.0.0.63:\> This example show you how to set your own prompt funtion by making the prompt your current IP Address of your prefferred DHCP connection. .INPUTS None .OUTPUTS None .NOTES The CPU_Memory prompt does create a job that runs in the background to grab the most current reading of the CPU usage. This job is removed whenver a new prompt is set. Three global variables are created with the use of this function prompt_FGColor,prompt_BGColor, and Prompt_Prefixblock. These are necessary to be in the global scope so that the prompt function can read them. #> function Set-Prompt{ [CmdletBinding(DefaultParameterSetName = "Name")] param( [Parameter(ParameterSetName = "Name")] [ValidateSet("Random_Cmdlet","Time-short","Time-long","Date-Time","Random_Fact","Measure_Command","CPU_Memory","System_Uptime")] [string]$Name, [Parameter(ParameterSetName = "Name")] [ValidateSet("Admin","Time","Error_Count","Debug")] [string[]]$Prefix, [ConsoleColor]$ForegroundColor = $host.UI.RawUI.ForegroundColor, [ConsoleColor]$BackGroundColor = $host.UI.RawUI.BackgroundColor, [Parameter(ParameterSetName = "Custom",Mandatory)] [scriptblock]$ScriptBlock ) New-Variable -Name prompt_FGColor -Value $ForegroundColor -Scope Global -Force New-Variable -Name prompt_BGColor -Value $BackGroundColor -Scope Global -Force New-Variable -Name Prompt_Prefixblock -Value $null -Scope Global -Force if (Get-Job -Name CPU_Mem_Prompt -ErrorAction SilentlyContinue){ Remove-Job -Name CPU_Mem_Prompt -Force } $Global:Prompt_Prefixblock = foreach ($Pre in $Prefix){ switch ($Pre){ "Admin" {{ $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = [Security.Principal.WindowsPrincipal] $identity $adminRole = [Security.Principal.WindowsBuiltInRole]::Administrator if ($principal.IsInRole($adminRole)){ "[Admin]" } else{ "[Non-Admin]" } }} "Time" {{ "[$((Get-Date).ToShortTimeString())]" }} "Error_Count" {{ "[$($Error.Count)]" }} "Debug" {{ if (Test-Path variable:/PSDebugContext) {'[DBG]'} }} } } if ($PSBoundParameters.ContainsKey("Scriptblock")){ New-Item -Path function:prompt -Value $ScriptBlock -Force | Out-Null return } switch ($Name) { "Random_Cmdlet" { function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$((Get-Command -Verb ((Get-Verb).Verb | Get-Random) | Get-Random).name):\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "Time-short" { function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$((Get-Date).ToShortTimeString()):\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "Time-long" { function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$((Get-Date).ToLongTimeString()):\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "Date-Time"{ function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$((Get-Date).ToString()):\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "Random_Fact" { function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$((Invoke-RestMethod -Method Get -Uri 'https://uselessfacts.jsph.pl/random.json?language=en').Text)`nPS :\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "Measure_Command" { function global:prompt { $lastcommand = Get-History | select -Last 1 $timespan = New-TimeSpan -Start $lastcommand.StartExecutionTime -End $lastcommand.EndExecutionTime if ($timespan.Minutes -lt 1){ if ($timespan.Seconds -lt 1){ Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalMilliseconds,2)) milliseconds:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalSeconds,2)) seconds:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalMinutes,2)) minutes:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "CPU_Memory" { Start-Job -Name CPU_Mem_Prompt -ScriptBlock { get-counter -Counter "\processor(_total)\% processor time","\memory\% committed bytes in use" -Continuous | foreach {"CPU: $([math]::Round($_.CounterSamples.cookedvalue[0]))% | Mem: $([math]::Round($_.CounterSamples.cookedvalue[1]))%:\>"} } | Out-Null function global:prompt { $jobresults = Receive-Job CPU_Mem_Prompt | select -First 1 if ($jobresults){ Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$jobresults" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } $results = get-counter -Counter "\processor(_total)\% processor time","\memory\% committed bytes in use" | foreach {"$(($Prompt_Prefixblock | foreach {&$_}) -join '')CPU: $([math]::Round($_.CounterSamples.cookedvalue[0]))% | Mem: $([math]::Round($_.CounterSamples.cookedvalue[1]))%:\>"} Write-Host $results -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } "System_Uptime" { function global:prompt { $Lastboot = (Get-CimInstance -ClassName Win32_OperatingSystem).LastBootUpTime $timespan = New-TimeSpan -Start $Lastboot -End (Get-Date) if ($timespan.TotalDays -lt 0){ if ($timespan.TotalHours -lt 0){ Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalMinutes,2)) minutes:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalHours,2)) hours:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')$([math]::round($timespan.TotalDays,2)) days:\>" -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } default { function global:prompt { Write-Host "$(($Prompt_Prefixblock | foreach {&$_}) -join '')PS $($executionContext.SessionState.Path.CurrentLocation)$('>' * ($nestedPromptLevel + 1)) " -ForegroundColor $prompt_FGColor -BackgroundColor $prompt_BGColor -NoNewline return " " } } } } function Start-Shutdown { [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias("CN","Name","MachineName")] [string[]]$ComputerName = $ENV:COMPUTERNAME, [Parameter()] [ValidateSet("Shutdown","Reboot","PowerOff")] [string]$ShutdownType = "Reboot", [Parameter()] [int]$Delay = 0, [Parameter()] [ShutDown_MajorReason]$Major_ReasonCode = [ShutDown_MajorReason]::Other, [Parameter()] [ShutDown_MinorReason]$Minor_ReasonCode = [ShutDown_MinorReason]::Other, [Parameter()] [string]$Comment, [Parameter()] [switch]$Force, [Parameter()] [switch]$Unplanned ) begin { if ($Force){ $Flags = ([ShutDownType]$ShutdownType).value__ + 4 } else{ $Flags = ([ShutDownType]$ShutdownType).value__ } $Planned_ReasonCode = (0x80000000) * -1 if ($Unplanned){ $ReasonCode = $Major_ReasonCode.value__ + $Minor_ReasonCode.value__ } else{ $ReasonCode = $Major_ReasonCode.value__ + $Minor_ReasonCode.value__ + $Planned_ReasonCode } if (!($PSBoundParameters.ContainsKey("Comment"))){ $Comment = "$Type command sent from user $ENV:USERNAME on computer $ENV:COMPUTERNAME with a delay of $Delay seconds" } $ShutdownParamters = @{ Flags = $Flags Comment = $Comment ReasonCode = $ReasonCode Timeout = $Delay } } #begin process { foreach ($computer in $ComputerName){ if (Test-Connection -ComputerName $computer -Quiet -Count 1){ Try{ $session = New-CimSession -ComputerName $computer -OperationTimeoutSec 1 -ErrorAction Stop } catch{ try{ Write-Information "Unable to connect to $computer with Wsman, using DCOM protocl instead" -Tags Process $session = New-CimSession -ComputerName $computer -SessionOption (New-CimSessionOption -Protocol Dcom) -ErrorAction Stop } catch{ Write-Error "Unable to connect to $computer with Wsman or Dcom protocols" continue } } $Win32_OperatingSystem = Get-CimInstance -ClassName Win32_OperatingSystem -CimSession $session $ReturnCode = (Invoke-CimMethod -CimInstance $Win32_OperatingSystem -MethodName Win32ShutdownTracker -Arguments $ShutdownParamters -CimSession $Session).ReturnValue $session | Remove-CimSession if ($ReturnCode -eq 0){ [PSCustomObject]@{ ComputerName = $computer ShutdownType = $ShutdownType ReasonCode = "$($Major_ReasonCode): $Minor_ReasonCode" Delay = $Delay CommandSuccessful = $true } } elseif ($ReturnCode -eq 1191){ Write-Error "$ShutdownType action Failed for $($computer): The system shutdown cannot be initiated because there are other users logged on to the computer, use the -Force parameter to force a shutdown operation($Returncode)" } elseif ($ReturnCode -eq 1190){ Write-Error "$ShutdownType action failed for $($computer): A system shutdown has already been scheduled.($ReturnCode)" } else{ Write-Error "$ShutdownType action failed for $($computer): Reason code $ReturnValue" } } #if else{ Write-Warning "$computer is unreachable" } } #foreach } #process } <# .SYNOPSIS This function will abort a scheduled shutdown. .DESCRIPTION This function uses the shutdown.exe utility to abort a scheduled shutdown. If no error was given then the abort action was successful. .PARAMETER ComputerName Specifies the computers the scheduled shutdown (if any) will be stopped on. Type computer names or IP addresses. .PARAMETER Passthru Returns the results of the command. Otherwise, this cmdlet does not generate any output. .EXAMPLE PS C:\> Stop-Shutdown -ComputerName Client01v -Passthru ComputerName ShutdownAborted ------------ --------------- Client01v True This example aborts a scheduled shutdown on computer Client01v and uses the passthru parameter to output an object that tells you if the abort was successful or not. .INPUTS System.String ComputerName - The name of the computer to abort the action .OUTPUTS None .NOTES Requires Admin for remote computer abort actions and shutdown.exe #> function Stop-Shutdown{ [CmdletBinding()] param( [Parameter(ValueFromPipeline,ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias("CN","Name","MachineName")] [string[]]$ComputerName = $ENV:COMPUTERNAME, [switch]$Passthru ) Process{ foreach ($computer in $ComputerName){ Write-Information "Sending abort command to $computer" -Tags "Process" shutdown /a /m "\\$computer" 2> $null if (!$?){ if ($Passthru){ Write-Information "Passthru paramter was used, creating object for unsuccessful abort action for $computer" -Tags "Process" [PSCustomObject]@{ ComputerName = $Computer ShutdownAborted = $false } } else{ $PSCmdlet.WriteError($Error[0]) } } elseif ($Passthru){ Write-Information "Passthru paramter was used, creating object for successful abort action for $computer" -Tags "Process" [PSCustomObject]@{ ComputerName = $Computer ShutdownAborted = $true } } } #foreach } #process } <# .SYNOPSIS Verifies that a given credential is valid or invalid. .DESCRIPTION Will test a given username with a given password and return either true or false. True if the credentials provided are valid and false if they are not. .PARAMETER UserName The username you want to test the credentials for. Accpets pipeline input. .PARAMETER Password The password you want to test with the UserName that was provided. Requires a secure string to be inputted. .EXAMPLE PS C:\> Test-Credential -Credential "MrPig" True This example shows you can enter in just a username and it will prompt for the password, it return "True" which indicates that the credentials are valid. .EXAMPLE PS C:\> Test-Credential cmdlet Test-Credential at command pipeline position 1 Supply values for the following parameters: Credential False If you do not enter in any parameters it will prompt for Credentials. Since credentials enter in this example were not valid it return a false boolean value. .EXAMPLE PS C:\> Test-Credential -UserName syrius.cleveland -Password (Read-Host -AsSecureString) *********** False This example uses the Read-Host -AsSecureString command to provide the value for the password and filles our the UserName parameter beforehand. Since credentials enter in this example were not valid it return a false boolean value. .INPUTS None .OUTPUTS Boolean .NOTES Requires secure string for password. I made the Output just a simple boolean value since the rest of the cmdlets that have test as the verb do the same. #> function Test-Credential{ [Cmdletbinding(DefaultParameterSetName = "Credentials")] [OutputType([bool])] param( [Parameter(Mandatory,ValueFromPipeline,ParameterSetName="Credentials")] [pscredential]$Credential, [Parameter(ParameterSetName="IsAdmin")] [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName, ParameterSetName="UserNameandPassword")] [String]$UserName = $ENV:USERNAME, [Parameter(Mandatory,ValueFromPipeline,ValueFromPipelineByPropertyName, ParameterSetName="UserNameandPassword")] [securestring]$Password, [Parameter(ParameterSetName="IsAdmin")] [switch]$IsAdmin ) Begin{ Write-Information "Adding System.DirectoryServices.AccountManagement assembly" -Tags "Begin" Add-Type -AssemblyName System.DirectoryServices.AccountManagement Write-Information "Checking to see if computer is part of a domain using Get-CimInstance" -Tags "Process" $PartofDomain = (Get-CimInstance -ClassName Win32_ComputerSystem).PartOfDomain if ($PartofDomain){ $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain } else{ $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Machine } } Process{ try{ $Previous = $ErrorActionPreference $ErrorActionPreference = "Stop" if ($IsAdmin){ if ($PartofDomain){ $Identity = [System.Security.Principal.WindowsIdentity]::new($UserName) $WinPrincipal = [Security.Principal.WindowsPrincipal]::new($Identity) $Admin = $WinPrincipal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) Write-Information "Username $Username is admin: $Admin" return $Admin } else{ $Admingroupmember = (Get-LocalGroupMember -Name Administrators).Name | foreach {$_.Split('\',2)[1]} $Admin = ($Admingroupmember -contains $UserName.Split('\',2)[0]) return $Admin } } if ($PSCmdlet.ParameterSetName -eq "UserNameAndPassword"){ $Credential = [System.Management.Automation.PSCredential]::new($UserName,$Password) } $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ContextType) Write-Information "Validating Credentials" -Tags "Process" $ValidatedCreds = $PrincipalContext.ValidateCredentials($Credential.UserName,$Credential.GetNetworkCredential().Password) Write-Information "Username $($Credential.UserName) with provided password resulted in: $ValidatedCreds" -Tags "Process" $ErrorActionPreference = $Previous return $ValidatedCreds } catch{ $ErrorActionPreference = $Previous $PSCmdlet.WriteError($_) } } #Process } function New-CimConnection { [CmdletBinding()] param( [string]$ComputerName, [Parameter()] [ValidateSet("WsMan", "Dcom")] [string]$Protocol = "WsMan" ) $CimSessionOption = New-CimSessionOption -Protocol $Protocol Try{ $CimSession = New-CimSession -ComputerName $ComputerName -SessionOption $CimSessionOption -OperationTimeoutSec 1 -ErrorAction Stop } catch{ try{ switch ($Protocol){ "WsMan" {$CimSessionOption = New-CimSessionOption -Protocol "Dcom"; $Backup = "Dcom"} "Dcom" {$CimSessionOption = New-CimSessionOption -Protocol "WsMan"; $Backup = "WsMan"} } $CimSession = New-CimSession -ComputerName $ComputerName -SessionOption $CimSessionOption -OperationTimeoutSec 1 -ErrorAction Stop Write-Warning "Unable to connect to $ComputerName with $Protocol, using $Backup protocol instead! Try using setting the Protocol parameter to $Backup for faster execution time." } catch{ $PSCmdlet.WriteError($_) } } $CimSession } Export-ModuleMember -function Add-RemoteDesktopUser, Get-BatteryStatus, Get-DefaultPrinter, Get-LoggedInUser, Get-MonitorInfo, Get-RemoteDesktopUser, Get-sysLocalGroupMember, Remove-RemoteDesktopUser, Set-Prompt, Start-Shutdown, Stop-Shutdown, Test-Credential |