GPO – Comments

GPO – Comments

Did you know you can add a comment to a group policy? Did you know you can report on that comment as well. Basically, if you put the purpose and any additional information like security groups, applied to and not to, and so on and so forth, you can document and report on the group policy using PowerShell. Let me show you how.

Commenting Group Policy

There are two ways you can comment group policy, the first is the GUI. The second is through PowerShell. First we will do a comment via the GUI.

The GUI Way

  1. 300start Group Policy Management Console
  2. Select the policy object you wish to comment
  3. Right click and click edit on this policy
  4. In the navigation plane, right click the policy’s name and select properties
  5. Click the Comment tab
  6. Enter your comment
  7. Apply and Ok.

The PowerShell Way

The command we are going to be using is the get-gpo command. This command allows you to grab the single GPO or all of the GPOs in the company.

Get-GPO -Name "Control Panel Access"
Results of Get-GPO

You can see the comment we added to the policy earlier as the description. Why Microsoft hasn’t changed the name on the tab, I will never know. For anyone who is familiar with PowerShell there is no Set-GPO commandlet native to PowerShell. So, it’s time to put our thinking caps on and figure out what we can do. I piped this command into get-member.

Get-GPO -Name "Control Panel Access" | Get-Member
Results of the get member

Look what we found. The Description property has a set feature to it. This means you can set the property using the get-gpo command.. Bingo! Lets do this!

(Get-GPO -Name "Control Panel Access").Description = "This policy blocks access for all members of the employees group, except for those inside the sg_control_panel_access group from accessing control panel. 04/25/2021"
Results from the above command.

I wanted to add today’s date into the description. This way whoever comes behind me sees when it was written. Now let’s see if the GUI updated accordingly as well.

The new information is inside the policy

It updated like we believed it would. This is because of the set feature inside the property. The command allowed us to set the property. Such a simple and yet lovely thing. Now, you can run a report asking for just the name and description to get much more information. If you do this with all of your policies, life gets a little easier.

Maybe, one day I will use a dynamic parameter and create a set-GPOdescriptoin command to set the description of any gpo you can select from. Hum…

Like always, if you have any questions feel free to ask.

GPO – Control Panel Chaos

GPO – Control Panel Chaos

Onboarding a new client can take some time. Getting to know the client, their environment, and much more is very important. While talking with a client, the client needed to extend the cache on the mailbox so they could search past 12 months. Not a problem, just slip into the control panel, select mail, double click the update button, and off to the races we go. Simple as that right? Well, the problem, control panel said, nope. The control panel was disabled. Not my first time seeing this. Of course, I went through the start menu to get to the mailbox options. Later I needed to uninstall some security software to load our software. I went to the control panel, and it said nope. At this point, I was a little shocked. Who would block even domain admins from working with the control panel? This either was a registry hack or a group policy. My money was on the group policy.

The Problem

All users accessing control panel is prompted with the above prompt. This even applies to the domain admin accounts.

Steps of Troubleshooting

I started from the local computer as I had access to the local computer at the time. The first thing I did was run the gpresult /r command. This command will show you the applied group policies. Here I found a policy called “Control Panel Access”. Which I had to thank the admin who named it. Having good names for your group policies is very important.

Next, I remoted into the server listed in the command’s results “Group Policy was applied from”. I started up the Group policy and Found the policy. I checked the comment section for any documentation, none was found. But I did find this …

I was taken a little back at this setup. The policy (Green arrow) prohibits anyone that has this policy applied to them to be able to access the control panel. The GPO status (Purple arrow) shows that it is enabled. So it’s working. Our error message proved that. Here is the kicker. The security filtering was set to authenticated users. Basically, someone didn’t finish setting this up. Logically, you want to limit access to programs within the control panel. Then delegate access to those who need to be able to access those settings accordingly.

The next step was to send my findings to the client. We spoke for a while and It was finally decided that a group of people would have access, but everyone else, wouldn’t. This was admins, laptop users, and a few others.

The Real Problem

Let us talk about the real problem for a while. The real problem here is the authenticated users. This is similar to the everyone group in the active directory. If they have a user account that authenticates with AD, then the policy will apply because the “apply” flag is set. The second part of this is the policy was applied on the top level. This means the policy was applied to everyone. That’s an “Oh, crap” moment for any admin.

The authenticated user is very important and almost every group policy will have to use it. However, I can only count on a few finger cases where you will need to set the apply flag. What do I mean by that? Click on the delegation tab and you will see how the policy is delegated. How gets it and who doesn’t.

The best way to see how these settings are set on a user is to click the user in question (Authenticated users) and click advanced. From here it will look and feel like a file permissions with a few little differences.

Here you see the authenticated users have the read flag set. This means any user can read this policy. This allows the system to determine if and where the policy should be applied. If this is set to deny, then no one will be able to read and thus no one would be able to apply. Read is a basic requirement for most policies. However…

The apply is what is causing the issue here. If we uncheck this, then the policy would not apply to any users. This isn’t the fix we are looking for, but it’s a start. There are no other groups that have “apply” attached to this policy. So it’s time to make some.

The Resolution

The resolution is simple,

  1. we created a security group called SG_Control_Panel_Access and added our users into the group.
  2. We added the Employees group to the policy as a read/allow apply/allow.
  3. Then we added the sg_control_panel_access to the policy as a read/allow, apply/deny.
  4. Finally, we unchecked the apply/allow from the authenticated users.
  5. completed a gpupdate /force on the end user’s computer and gpresult /r.

So, what does this work? The Employees group is the group that stores all active employees. Most organizations have this group or a group like it. It normally sits in their templates or whatnots. We give employees the right to read and apply. This “allow” access will make sure the policy applies to everyone in that group.

The second part of this is the deny option to the “apply” for the sg_control_panel_access group. Deny will take precedence over allow every time, except for authenticated users. Thus, when the account reads the policy, it chooses not to apply it because the flag said to. Thus, the policy never applies. Thus, the user has access to the control panel. YAY!

The last part is to force an update to the group policy for that user/computer. gpupdate /force is magical as it speeds up the process. It tells the computer to go to group policy and get the information and update the policy. Sometimes this requires a logout, but not most times. The gpresult /r allows you to see if the policy did not apply.

At this point, I tested with the users. The problem was fixed. I wanted to take it one step farther and move the policy from the top level to the user OU. This way the policy wouldn’t be overwritten by an OU level policy. After I presented this idea, I was able to delink it from the domain level to the OU level. This means that only the users are going to be effected and none of my service accounts.

Documentation

The final step to any project is documentation. The best way to document group policy when you or the client doesn’t have a robust documentation system is to use the comment section in the group policy.

  1. Right-click on the policy itself
  2. Click edit.
  3. In the console tree, right-click the name
  4. click properties
  5. On the next box, click the comment tab
  6. Enter a useful comment.

The clearer details you can give, the better. In this case, we explained what it does, block access for the members of a group called employees except for those inside a control panel access security group from accessing the control panel.

Make sure you test, and document. If you don’t have a test environment, like in this case, then make one of your own and test. Test, and double test. Always document inside your document control, and in the policy itself. This way, if the document control dies, the policy has the comment. If you have any questions, feel free to ask away.

User Terminations, Standard Method for emails

User Terminations, Standard Method for emails

As I have been in IT, i have seen more than one way to handle emails after a user has left, both on-prem and off prem. This setup is for office 365/exchange online.

Shared Mailboxes And Forwarder

Overview

This method is based on a variation of how Microsoft suggests doing it with Office 365. But it doesn’t limit the amount of time required. The Basic Idea is to convert a mailbox from a standard user to a shared mailbox. Then Grant the user who needs access to the mailbox access. This way they can go into the OWA and access past emails.

Details

There are hundreds of documents out there showing you how to do this, It’s not a hard process at all. Why not another!

  1. Log into the O365 client using either your user/exchange admin or global admin account.
  2. Click the Admin Center button.
  3. Next, Click the Exchange Admin Button at the bottom left-hand side of the screen.
  4. Now you are in the Exchange Admin Center. If you are familiar to exchange on-prem, it doesn’t look anything like that but has similar flows. Click the recipient button on the left.
  5. Click the Mailbox button.
  6. Search for the user you wish to edit and click on the user. The user menu will appear on the right.
  7. Click the Convert to shared mailbox option.
  8. The next screen will want you to confirm this action. Do so by clicking confirm.
  9. Exchange Online will convert this mailbox into a shared mailbox. It can take up to 30 minutes from my experience for this to happen. Once the mailbox is converted, the user plane will change and say shared mailbox.
  10. Once the mailbox is a shared mailbox, indicated by saying shared mailbox under the display name. We need to grant permissions. To do that we click the Manage Mailbox delegation.
  11. Once in here, click the edit button for the read and manage option. I have never seen someone use the send as an option for former employees, but I have seen it for something like the marketing mailbox at a larger company.
  12. Here you have two options, You can either type out the name, or click the add permissions button and search that way. The add permissions button list all of the users in the company, while the search box just searches by display name or email address.
  13. Make sure you are clicking save with each change or it will not be changed.
  14. Add the permissions accordingly and click save.
  15. Now we must set the forwarder. Click Manage Mailflow settings.
  16. Click the edit button for Email forwarding.
  17. Next, toggle button the Forward all emails sent to this mailbox
  18. The search box will appear. Search for the user and click add.
  19. Click Save.

Pros

  • Previous emails are saved.
  • Can have multiple people accessing the box together through delegations.
  • Can set send as to mimic the previous employee’s presence.
  • Can set strict permissions to who and what can see what.
  • Saves a license.
  • Simple and industry standard.
  • Forwarder set.

Cons

  • Forwards to the single user, not multiple users.
  • Set and forget. Often times you will end up with a large number of these boxes and will need to routinely clear them out.
  • Mobile apps tend to have issues with shared mailboxes.

Use Case

As this is industry standard, it’s very userful. A user leaves, convert the mailbox to a shared. It no longer has a password to worry about. Your users can gain direct access to the mailbox with outlook, or OWA.

Scripted Way

That’s right every way can be scripted. Here is the script for this method.

$Username = Read-Host "Please enter the username of the employee"
$Delegets = Read-Host "Please Enter the name of the delegate"
set-mailbox $Username -Type Shared
Add-MailboxPermission -Identity "$Username" -User $Delegets -AccessRights FullAccess -InheritanceType All

Adding Members Within Members

Adding Members Within Members

Nested objects are very useful as they can give you a way to store complex data. The featured image of this blog is of a nested temple. Each room is nested on top of the other. What’s cool about this image is each room has items inside of it. For example, there a water bottle. Think of nested arrays like this. The full thing is a temple. That is the first object. Then we have small buildings inside that. Those are our secondary objects. Those secondary objects also have properties. So imagine the roof being the second object’s properties. Inside each building there are rooms. Those are our third objects. What’s in those rooms are the properties of those rooms. Then you can have a chest that could be a fourth object and it could have its own properties. As you can tell, this can get confusing quickly. Parsing through that data can be a challenge. Below we will go through the process of adding display names to the account SKU for the licenses for each user. We do this for later reporting. There are a few things you will need before we start.

  1. MSonline module
  2. SKU file that can be found here
  3. A Microsoft account with users
  4. An at least read-only account.

The Setup

You will need to be doing all of the following commands on PowerShell Version 5 because MSOnline does not work on PowerShell 7 yet.

Install-Module MSOnline -force
Import-module MSOnline

Now you need to connect to the Microsoft Online account. You can do this by using the connect-msolservice. Make sure you have the credentials required.

$Users = Get-MsolUser -all

Now download the list here. You will need to import the file into PowerShell. You can do this with the Import-CSV command. Inside this list, you can add what you want and so on and so forth.

Add the Add to Licenses

Now we have the array built, it’s time to start learning something cool. The goal is to add the display name of the Account SKU inside the licenses area of each user. This way when you see the licenses you know what you are looking at later. More importantly, this will teach you how to place information directly into an array instead of rebuilding the array.

For vs Foreach

I love foreach, it helps so much. foreach ($user in $users) is nice. This is great for rebuilding an array because you can push the information into another array easily like this. Great for a full rebuild. However, in our world we want to put the data directly into the array itself. The Foreach creates a temporary object based on the object that is inside the array. So it creates a “User” based on the “Users.” Thus, doing $user | add-member doesn’t stay. I tried this a few hundred times. So, the for loop is going to be our friend. The reason for this is you can say where in the original loop you want to go to. Lets start the loop shall we.

The Script

for (($I = 0); $I -lt $Users.count; $I++ ) {
    if ($Null -ne $Users[$I].Licenses) {
        for (($A = 0); $A -lt $Users[$I].Licenses.count; $A++) {
            $Los = $Users[$I].Licenses[$A].AccountSkuID
            $Lose = $SKU | where-object { $_.SKU -like $Los }
            if ($Null -ne $Lose) {
                $Users[$I].Licenses[$A] | Add-Member -MemberType NoteProperty -Name "LiceDisplayName" -Value $Lose.Name
            }
            else {
                $Users[$I].Licenses[$A] | Add-Member -MemberType NoteProperty -Name "LiceDisplayName" -Value "N/A"
            }
        }
    }
}

The first part is the for loop. We start the loop at 0. ($I = 0) we start at 0 because arrays start at 0. Then we will continue this loop while $I is less than the total user count. Each loop will increase the $I by 1 using the $I++. This is a basic for loop setup. This will give us the index of the users. The next step is the test to see if the user has licenses.

if ($Null -ne $Users[$I].Licenses) {}

A few things with this if statement. First the $Null is on the left. The reason for that is because we evaluate nothing first. Then if the next thing even has something in it, it triggers right off the bat. If it doesn’t, then we else out. Thus, having $Null on the left is faster. Next notice the $Users[$I]. This is basically we want the Object at index of $I. Each time the loop process, that $I increases. Thus, we go through the loop. Finally we are looking at the .Licenses. If It doesn’t have a licenses, we don’t want to evaluate anything. We will leave this alone. Now if it does, then we get to have our fun. Normally users have more than one license inside their profile. Thus, we have more than one loop.

for (($A = 0); $A -lt $Users[$I].Licenses.count; $A++) {}

Now notice, I replaced $I with $A, but I changed up the condition. If $A is less than the current index of $users licenses count. Then we increase $A by 1 with the $A++. Now we are within the user array within a user and evaluating licenses. We are going deep.

$Los = $Users[$I].Licenses[$A].AccountSkuID
$Lose = $SKU | where-object { $_.SKU -like $Los }

So here we are grabbing that AccountSkuID with our $Los variable. Then we compare from our Imported $SKUs we downloaded and imported earlier. We place that inside the $Lose value. So now we have the Displayname and the SKU name.

To prevent any errors, we check $Lose to see if it’s null. I it is, then we want to place the information into it differently. This way later when we look at the data we don’t get some odd errors.

if ($Null -ne $Lose) {}

Finally the meat and potatoes of why we are here. We want to add that Display name for that sku. So let’s do that. We want the current $I user. We want the current $A licenses. Once we have both of those, we want to use add-member to add a note property. We want to name it something different than just DisplayName because that is everywhere. So, I like to use smaller names. I used $Lice. You can use Displayname, or not. I’m just odd.

$Users[$I].Licenses[$A] | Add-Member -MemberType NoteProperty -Name "LiceDisplayName" -Value $Lose.Name

As we loop through this process, it will add the Display name to each license inside the array. This way you can report on it later. In the else part of our test, we place the note property name the same but the value would be “N/A” or something along those lines. As long as it’s not null, we are safe. Below is a visual that might help understand how this loop works. The User object (Black) is the I. The LIcenses are multi-color. A. All of it lives within the User Array which is Purple.

If you have any questions, please feel free to reach out.

Upgrade Windows – Dell

Upgrade Windows – Dell

I have a love hate relationship with dell. When it comes to windows 10 upgrades, I really don’t like them. How do you mass push windows 10 upgrade to clients without breaking them. As many of you know, recently the 20H2 update has broken many different dells. So, a quick way to fix this is by comparing the model to the online list. This script isn’t that big, and is designed to be deployed from the end user’s machine as a system service or admin account.

The Script

$DellSite = Invoke-WebRequest -Uri "https://www.dell.com/support/kbdoc/en-us/000180684/dell-computers-tested-for-windows-10-october-2020-update-and-previous-versions-of-windows-10" -DisableKeepAlive
$Dellraw = $DellSite.Rawcontent.split('`r')
$CPInfo = Get-ComputerInfo
if ($Dellraw | select-string $CPInfo.csmodel) {
    if (!(Test-Path "$($env:SystemDrive)\Temp\Win10Upgrade")) {New-Item c:\temp\win10upgrade -Type directory}
    $DateTime = (Get-date).ToString("yyyy-MM-dd_hh-mm-ss")
    $webClient = New-Object System.Net.WebClient
    $url = 'https://go.microsoft.com/fwlink/?LinkID=799445'
    $file = "$($env:SystemDrive)\Temp\win10upgrade\Win10Update_$DateTime.exe"
    $webClient.DownloadFile($url, $file)
    Start-Process -FilePath $file -ArgumentList '/auto Upgrade /quiet /noreboot'
} else {
    Write-Error "$($env:COMPUTERNAME) is a $($CPInfo.CsModel) and is not on the approved list found at: https://www.dell.com/support/kbdoc/en-us/000180684/dell-computers-tested-for-windows-10-october-2020-update-and-previous-versions-of-windows-10"
}

The Breakdown

I’m glad you decided to stay for the breakdown. This breakdown isn’t going to take long. The first element of this break down is the invoke-webrequest. We capture the website with the required information. (Link). Then we split the raw content by the return carriage.

$DellSite = Invoke-WebRequest -Uri "https://www.dell.com/support/kbdoc/en-us/000180684/dell-computers-tested-for-windows-10-october-2020-update-and-previous-versions-of-windows-10" -DisableKeepAlive
$Dellraw = $DellSite.Rawcontent.split('`r')

Now our web data is ready to pull from. Now we need to get information from the computer itself. Most systems these days have the Get-ComputerInfo command on them. It pulls the system info on a computer. Next, we ask a simple if-then statement. If the $DellRaw has the model number, then download and install the upgrade, if not let us know. Basically, we need a bouncer at this point. We use the $CPInfo.CSmodel as this is the model number.

$CPInfo = Get-ComputerInfo
if ($Dellraw | select-string $CPInfo.csmodel) {
    #Install the upgrade
} else {
    #Warning the installer program that it's not on the list. 
}

The Download and Install

We first ask if the file folder is there. If it isn’t then we create it using the new-item command. Then we want to create a datetime stamp. Like in previous blogs, we use the .tostring() method to format the output. Then we declare the url and file path. Next, we invoke-webrequest command and download the file using the -outfile flag. Finally, we start the install with the correct flags. In this case, we want the install to be an upgrade that is silent and doesn’t force a restart because it could be in the middle of the day when this thing is finishing up. To start the installer we use the start-process

if (!(Test-Path "$($env:SystemDrive)\Temp\Win10Upgrade")) {New-Item c:\temp\win10upgrade -Type directory}
$DateTime = (Get-date).ToString("yyyy-MM-dd_hh-mm-ss")
$url = 'https://go.microsoft.com/fwlink/?LinkID=799445'
$file = "$($env:SystemDrive)\Temp\win10upgrade\Win10Update_$DateTime.exe"
Invoke-WebRequest -Uri $url -OutFile $file
Start-Process -FilePath $file -ArgumentList '/auto Upgrade /quiet /noreboot'

If your computer model is not on the list we will need to do a write-error. It’s best to say the computer name, model name, and the website it is pulling the data from. The write-error is collected by the standard deployment software.

Write-Error "$($env:COMPUTERNAME) is a $($CPInfo.CsModel) and is not on the approved list found at: https://www.dell.com/support/kbdoc/en-us/000180684/dell-computers-tested-for-windows-10-october-2020-update-and-previous-versions-of-windows-10"

I hope this helps. Y’all have a great day now you hear.