Log Off That User!

Have you ever need to log a user off of every computer in your company at once? I’m going to keep this post short and sweet with just the core code. Lets take a look shall we. Here is the first part of the code.

$return += (((quser /server:$ComputerName 2> $null) -replace ‘^>’, ”) -replace ‘\s{2,}’, ‘,’) | ForEach-Object {
if ($_.Split(‘,’).Count -eq 5) {
Write-Output ($_ -replace ‘(^[^,]+)’, ‘$1,’)
} else {
Write-Output $_
}
} | ConvertFrom-Csv
$Return = $Return | Where-Object {$_.”IDLE TIME” -like “.”} | ForEach-Object {$_.”IDLE TIME” = $null}

In this code, we are using an old tool called quser which produces a string output of all the current users. So, we need to work with that information. Here we replace the spaces with , and so on so forth. Since the output of disconnected is different and has more , than non-disconnected users we search filter it out with another replace. Then finally we replace all the idle time with nothing to prevent errors while sorting. Now we take all of this csv formatted data and push it into convertfromcsv to create a usable array. which we can use later.

The next part of the code is a simple logoff command. These are older commands.

logoff /server:$computername $Return.ID;

The logoff command requires a computer name and the id. Using the information above we can sort through the output with foreach loops and log the person out. If you place all of that inside another foreach for a list of computers, you can have the script log them off everything and everywhere.

And that’s it. Everything past this point is just fluff and/or error proofing. I hope you all enjoy.

Mute your computer Please!

Have you ever had a co-worker who doesn’t use headphones and has his volume all the way up. Well, I have one of those. I ask him all the time to lower the volume. He does for a while but then it’s back to full blast. It sucks. What can we do right? Well… We are IT. We have PDQ. Time to mute the computer.

The Package

The package is a simple powershell script. That deploys to a computer. The powershell version is 3 or higher and nothing else is needed other than the script and a good name.

  1. Open PDQ Deploy
  2. Create a new package
  3. Under Details, I named my package “Mute target PC”
  4. Under Condistions, I unchecked powershell 1 and 2.
  5. Under steps, I select powershell.
  6. I typed:
    1. $Obj = new-object -com wscript.shell
    2. $Obj.SendKeys([char]173)
  7. Then I saved it.

Yes, that’s all you need. Then you deploy the script using your admin user. Bam, the computer is muted. Now, I’m one of those people that wants to know if something was successful or not. So, we can add error corrections and exit codes if we like. That’s totally up to you.

With that said, there is other ways to change the volume altogether. However that’s beyond the scope of this blog. You can get more information here: StackOverflow

Don’t Give Up

Sometimes personal life can come at you hard. Sometimes it’s takes everything in you to complete your daily tasks. Don’t give up. Keep pushing forward. Have a smile, ask for help. Let people know what’s going on. Most importantly, do your best.

I recently hurt my back. I have a bulging disk and it hurts. The nerves running down my spine are bruised which makes sleeping hard. It makes picking things off the floor even harder. Simple tasks are a challenge, but you know what, It’s OK. I have allowed everyone around me to help, and people general want to help someone who goes out of their way to help them. Times like these shows you how people respect you.

Final Words, Don’t give up. You can do it. Let others in as well. It takes time, but it’s worth it.

Clearing out Groups

Do you need to clear out a users groups in one go. It’s easy.

(Get-aduser -Identity testman -Properties memberof).MemberOf | sort | ForEach-Object {(get-adgroup -Identity $_ -properties samaccountname).samaccountname | Remove-ADGroupMember -Members testman -Confirm:$false}

A single line, that’s right. A single line. So lets break it down

(Get-aduser -Identity testman -Properties memberof).MemberOf

This part grabs all of the group members. By default the memberof is in DistinguishedName formation. Thus, you can pipe it into the get-adgroup easily without any issues.

| sort |

This part sorts the outcome of the memberof. This way you can see what’s going on that is kind of cool. Piping into the sort is optionial.

ForEach-Object {(get-adgroup -Identity $_ -properties samaccountname).samaccountname | Remove-ADGroupMember -Members testman -Confirm:$false}

Then we pipe the sorted information into a foreach-object loop. We first grab the groups information with the get-adgroup. We want the samaccountname because Remove-adgroupmember doesn’t understand a
DistinguishedName.

(get-adgroup -Identity $_ -properties samaccountname).samaccountname

Then we pipe that information into a remove-adgroupmember command. If you notice it only has members and confirm. The member is the original username and the confirm is set to false.

| Remove-ADGroupMember -Members testman -Confirm:$false}

That’s it! Crazy right? If you want to see it happen, do a verbose.

State of warning, you will see an error pop up. That error is the default group.

Unique Usernames

Here is the world I live in. Our company must keep records of all those that have touched the core application for the life of the company. These records include the user account. This means, no deleting any usernames. We have a turn over rate of 50%. So, how do you create unique usernames for cChrissmith when you already have 3 Chris smiths without using numbers? The answer seems simple, middle names. Well… Middle names tend to be the same. We have two Chris Richard Smiths in the company. Now, lets throw in another block, the usernames can’t pass 10 char. This means, if the name is long, it has to be cut off at 10 char. The username can’t have any unique characters either and it must be lowercased. Thus the name can only have a-z. Also hyphenated requires to be a single last name. Which means, we have to select from one of the names… That gets messy, so making it a single thing makes it easier. Oh, the fun of the legacy system.

The Script

function Auto-FullNameUserName {
     
    [cmdletbinding()]
    param (
        [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true)][string]$FirstName,
        [Parameter(ValueFromPipelineByPropertyName=$true)][string]$MiddleName = $null,
        [Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true)][string]$LastName
    )
    $Return = @()
    $FirstNameOrig = $FirstName
    $LastNameOrig = $LastName
    if ($MiddleName -ne "") {$Name = "$FirstName $MiddleName $LastName"} else {$Name = "$FirstName $LastName"}
    
    $FirstName = $FirstName.Split('-')[0]
    $FirstName = $FirstName -replace '[^a-zA-Z]',''
    $LastName = $LastName.Split('-')[0]
    $LastName = $LastName -replace '[^a-zA-Z]',''
    


    #We create our default first letter of the first name and last name.
    $Username = "$($FirstName.Substring(0,1))$($Lastname)"

    #Next we limit the size if need be. Dovac has a 10 char cap. So, if the username is 10 chars, we cut it at the 10th char and place it back into username.
    if ($Username.Length -ge 10) {$Username = $Username.Substring(0,10);Write-Verbose "Limited"}

    #Then we do some old school testing to see if the username exists. If the username does not exist, we produce our answer.
    if (!(dsquery user -samid $Username)) {

        #We check to see if there is a middle name. If so that will become the inital.
        if ($MiddleName -ne "") {$Init = $MiddleName.substring(0,1)} else {$Init = $null}
        $Return += [pscustomobject][ordered]@{
            Name = $Name 
            GivenName = $FirstNameOrig
            Initial = $Init
            Surname = $LastNameOrig
            #Our standard requires lowercased usernames.
            Username = $Username.ToLower()
        }
            

        #If the username does exist we ask if we have a middle name to work with.
    } elseif ($MiddleName -ne $null) {

        #We add the first letter of the middle name to the mix.
        $Username = "$($FirstName.Substring(0,1))$($MiddleName.substring(0,1))$($Lastname)"
            
        #Then we test it to see if it is to long or not. If it is, we shorten it to 10 char.
        if ($Username.Length -ge 10) {$Username = $Username.Substring(0,10);Write-Verbose "Limited"}

        #If that username doesn't exist, then we produce our answer.
        if (!(dsquery user -samid $Username)) {
            if ($MiddleName -ne "") {$Init = $MiddleName.substring(0,1)} else {$Init = $null}
            $Return += [pscustomobject][ordered]@{
                Name = $Name 
                GivenName = $FirstNameOrig
                Initial = $Init
                Surname = $LastNameOrig
                Username = $Username.ToLower()
            }
                

            #However, if it does exist. Then we start working with our loop. 
        } else {
                
            #We will loop through each char of the first name adding it and shortening it then testing it until we get a username that is unique and works. 
                
            Write-Verbose "Name - $Firstname $Lastname"
            for ($A = 1; $A -le $FirstName.ToCharArray().count; $A++) {

                #We start off A as 1 because it A0 has aleady be tested. Then we loop through which pushes the first name towards the front. 
                $Username = "$($FirstName.Substring(0,$A))$($Lastname)"
                Write-Verbose "$Username"

                #We cut it down to size
                if ($Username.Length -ge 10) {$Username = $Username.Substring(0,10);Write-Verbose "Limited"}

                #Then we test it. If success, then we go with that, if not we loop again.
                if (!(dsquery user -samid $Username)) {
                    if ($MiddleName -ne "") {$Init = $MiddleName.substring(0,1)} else {$Init = $null}
                    $Return += [pscustomobject][ordered]@{
                        Name = $Name 
                        GivenName = $FirstNameOrig
                        Initial = $Init
                        Surname = $LastNameOrig
                        Username = $Username.ToLower()
                    }
                    Write-Verbose "Break"
                    Break
                }
            }
        }
    } else {
        #if we don't have a middle name to work with we skip stright to the loop. 

        for ($A = 1; $A -le $FirstName.ToCharArray().count; $A++) {

            #We start off A as 1 because it A0 has aleady be tested. Then we loop through which pushes the first name towards the front. 
            $Username = "$($FirstName.Substring(0,$A))$($Lastname)"
            Write-Verbose "$Username"

            #We cut it down to size
            if ($Username.Length -ge 10) {$Username = $Username.Substring(0,10);Write-Verbose "Limited"}

            #Then we test it. If success, then we go with that, if not we loop again.
            if (!(dsquery user -samid $Username)) {
                if ($MiddleName -ne "") {$Init = $MiddleName.substring(0,1)} else {$Init = $null}
                $Return += [pscustomobject][ordered]@{
                    Name = $Name 
                    GivenName = $FirstNameOrig
                    Initial = $Init
                    Surname = $LastNameOrig
                    Username = $Username.ToLower()
                }
                #Breaks this loop if we get a good username. 
                break
            }
        }
    }
    $Return
}

I use a process i like to call reverse physiology on this script. If it doesn’t work, then we try something else instead of if it works.

I hope you all like it.

Powershell Locked Users Locations

Have you ever had that single user that just seems to always be locked out for whatever reason? At my company we have this happen all the time. With limited resources at our disposal, sorting through logs is difficult. There could be thousands of windows security logs to pour through to find that single username. Without having a AD auditing tool to work with the logs, powershell is an amazing and simple solution. So, lets look at some basics.

  1. The Logs for locked out users is a security log.
  2. The log number is 4740 (Link)
  3. Powershell can find which domain controller has the PDCEmulator on it.
  4. Get-Winevent can grab logs from said server and parse through them based on the type and id number.

Get-LockedOutInfo

Lets look at the two parameters and why they are the way they are.

  1. Username
    1. Designed to find the username from the logs in question.
  2. DaysFromToday
    1. Shows how many days back of logs that need to be searched.

The beginning of the script does a few things.

  1. Setups the return array. This return array will be used in the end process to present the data.
  2. Grabs the domain controllers with Get-ADDomainController -filter *. We do this to grab all of the domains controllers. This is a smaller shop so it’s easier to do.
  3. Then we find out which ones has the Operation Master Roles, PDC Emulator. The PDC Emulator controls the log ins and outs and contains these logs. Get-ADDomainController -filter * | where-object {$_.OperationMasterRoles -contains “PDCEmulator”}
  4. Now we know which server to search we setup our filter table. It will contain the log type, log ID, and the starttime of the logs.
    1. Logname = security
    2. ID, aka the event id number is 4740 like from above.
    3. finally the starttime is the number of days back. (get-date).adddays(-$DaysFromToday)
  5. Finally we put it all together in the get-winevent command. Get-WinEvent –computername $PDCEmulator.hostname –filterhastable $filtertable.

There is a lot going on in the beginning and it all sets us up to parse through the logs it collects. The Get-Winevent command takes a while to complete. No matter how you go about it, this command is slow. I tried parsing out the information with where-objects, and the filterhastables, but neither sped up the process and it even put a load on the server which isn’t good for the environment. So, I kept it there collecting the information for me.

The process section of this script does the parsing of the logs for the single user. We start off with a loop for each event in the get-winevent command. The information is given in a mixture of strings and a array. This makes it hard to work with. Thankfully the Properties value has clear cut information. However, this information isn’t labeled. Thus, you have not know which one is what. Event.Properties[0].value is the username properties that we are looking for. So with each loop we look at this location and ask is it our username with an if statement. From there we create the return.

  1. Inside the loop the if statement looks like if ($Event.Properties[0].Value -like “$username”) {}
  2. The Return contains 3 values.
    1. The username which we grab from username
    2. The time the log was created which is $event.timecreated
    3. and finally the computer which the event was called from, $Event.Propterities[1].value

We loop through each of these items searching for that single user. Once we get all the logs parsed into the $Return array. We move to our end statement which is nothing more than the “$Return” value.

Known issues

There are a few issues with using powershell to get locked out information.

  1. It’s slow. There is no way around this fact. The larger the company, the more slower it becomes.
  2. Not all call computers, where they were locked out at, makes since.
    1. For example, if someone has an exchange email on their phone, a locked out might have the exchange server name. This means their email was locked out. Using this method means you don’t know if it’s outlook or a phone. That’s where your experience with your company comes into play.
    2. Another example is the persons computer may be locking out but they are still connected to their computer. From my experince this is due to a fileshare connection with a outdated password.
    3. Macs… Enough said
  3. Computers outside the domain will not show correctly. If the computer has a domain trust or is not part of the domain, the computer name will not reflect.
  4. Logging is not setup properly. If group policy isn’t setup to do your logs for AD. Then this script will have nothing to pull from.
  5. Miss matched logs. I have seen miss matched logs where the properties 0 isn’t the username and so on so forth. This is rarer, but I have seen it.

With all this said, I use this script daily. I even have it integrated with my universal dashboard. So, I hope you all enjoy it and find it easy to use.

The Script

function Get-LockedOutInfo {
[cmdletbinding()]
param (
[Parameter(ValueFromPipelineByPropertyName=$true,Mandatory=$true)][string]$Username = "",
[int]$DaysFromToday = 1
)
begin {
$Return = @()
$DomainControllers = Get-ADDomainController -Filter *
Write-Verbose "Grabbing Domain Controller $($DomainControllers.hostname)"
$PDCEmulator = ($DomainControllers | Where-Object {$_.OperationMasterRoles -contains "PDCEmulator"})
Write-Verbose "Grabbing PDC Emulator Controller $($PDCEmulator.HostName)"
Write-Verbose "Searching Locked Out Flags"
$FilterTable = @{
'LogName' = 'Security'
'Id' = 4740
StartTime = (Get-Date).AddDays(-$DaysFromToday)
}
$LockedOutEvents = Get-WinEvent -ComputerName $PDCEmulator.HostName -FilterHashtable $FilterTable -ErrorAction SilentlyContinue
} process { foreach ($Event in $LockedOutEvents) { if ($Event.Properties[0].Value -like $username) { $Return += [pscustomobject][ordered]@{ Username = $username Time = $Event.TimeCreated Computer = $Event.Properties[1].Value } } } } end { $Return }
}

Windows Updates

Recently at my work, we discovered windows updates were an issue. For us group policy and wsus server helped us resolve this issue. However, not everyone has a wsus server that can run windows updates through. Thus, today we will be going over different methods on how to avoid windows upgrades like the cursed 1809 upgrade. 

Method 1 – Registry

The registry is our friend. If you can’t push out group policy, then push out registry updates. All this method is going to do is set the registry to download and allow you to install the updates when you choose. So, Start Regedit and navaigate to: 

HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU

Create a Dword called AUOptions and set the value to 2. Then restart the computer. For now on the computer will notify you when it’s ready to download and when it is ready to install. Below is the batch command:

REG ADD HKEY_LOCAL_MACHINE\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU /v AUOptions /t REG_DWORD /d 2 /f

Batch works well, here is the powershell method of doing what we want.

New-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" -Name "AUOptions" -Value 2 -PropertyType DWORD -Force

Method 2 – Group Policy

This works best if you have a domain without a WSUS server or this process can be set on a local machine. This is assuming you have a mixed bag.

  1. Gpedit.msc
  2. Computer Configuration > Administrative Templates > Windows Components > Windows Update > Configure Automatic Updates
  3. Click Enable
  4. Under Configure Automatic Updating, select option 2 – notify for download and auto install.
  5. Select a day best suited for your people.

Now if, you have a standard environment aka business environment, then follow these steps:

  1. Gpedit.msc
  2. Computer Configuration > Administrative Templates > Windows Components > Windows Update > Windows Update for Business
  3. Here we have 3 items to set.
    1. Manage Preview Builds:
      1. Click Enable
      2. Set the behavior for receiving preview builds: “Disable Preview Builds”
      3. This opts you out of the insider stuff.
    2. select when preview builds and features updates are received
      1. Click Enable
      2. Select the windows readiness level for the update you want to receive: Semi-Annual Channel
      3. This will give you the twice a year updates.

Windows Update Warning

Keep in mind, windows updates contain security updates. The upgrades tend to break everything but don’t avoid them to long.

Welcome

Welcome to the blog. I hope you all enjoy. There will be some good and not so good stuff up in here. This blog is IT related because that’s what I know best. 

Who am I?

My name is David. I’m from South Carolina. I graduated from Greenville technical college with an associates degree. I have worked in I.T. since 2014. I started off as an basic helpdesk guy and built my way up. At one point there was a exodus of IT infrastructure employees. I had to learn a lot in a very short time. At the same time I lost my mom, dad and had a new baby in the house. Since then, I keep upgrading my knowledge, skills, and abilities each and every day. My goal is to be a teacher when I get to retirement. That’s why I am starting this blog.