Active Directory

Azure Active Directory – Assigning Groups to Applications in PowerShell

Azure Active Directory Applications have been around for a while, but it’s I’ve found it hard to find good information on them beyond the biggest benefit of Marketplace Apps.

Along with my Azure AD B2B journey (still in preview at time of writing), the option of pushing out something like a SharePoint Online site as an app is one of the jigsaw pieces required to make the whole B2B process work – as a version of the apps page is displayed as the default link to anyone who accepts an Azure AD B2B invite and logs in for the first time.

MyApps – an externally invited user will only see the apps they have access to (by default, none)

I’m trying to gloss over details here, as there’s a lot of steps with different parts of the Microsoft world to get a process automated end to end for inviting external users to a SharePoint Online site – but the last step of assigning a user or group to an application has no documentation I could find, that showed how to achieve this via PowerShell.

All I want to do here, is create an Application in Azure AD, then assign a group to it. Members of the group will then see the application on MyApps.

Two different modules are required – Azure Active Directory V2 PowerShell module and Azure Resource Manager.

What we can do with these two modules is first create the application with the New-AzureRMADApplication command:

New-AzureRmADApplication -DisplayName "SharePoint Online Site A" -HomePage "https://contoso.sharepointonline.com/sitea" -IdentifierUris "https://contoso.sharepointonline.com/sitea"

Easy, now you have an application that will point to the URL entered in Azure Active Directory. Assigning a group to it is a bit trickier…

First, a few values need to be obtained:

$app = Get-AzureRmADApplication | where displayname -eq "SharePoint Online Site A"
$appid = $app.ApplicationId
$fullgroup = get-msolgroup -all | where displayname -eq "SharePoint Online Site A"

This is getting the two objects as variables – the Application itself, and the group that you want to add onto it.

Then a new Service Principal needs to be created based on the Application, as this is required when adding the group onto the application:

New-AzureADServicePrincipal -AppId $appid

Another variable is needed, which is the new Service Principal we just created:

$servicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$appId'"

Finally, we can assign the group to the application:

New-AzureADGroupAppRoleAssignment -objectid $fullgroup.objectid -principalid $fullgroup.objectid -resourceid $serviceprincipal.objectid -id ([Guid]::Empty)

You can check that this has applied by the Azure Active Directory portal too, by going to your Active Directory section, choosing ‘Applications’ and finding your app, then go into ‘users and groups’ and find the group. You should see a ‘yes’ in the assigned field.

If there’s any interest in documenting the entire SharePoint Online and Azure AD B2B invite process and script, let me know. It’s a great way of sharing data with clients via a portal.

AzureAD – Assign Application to User via PowerShell

Scenario:

You’ve created an application in Azure AD, and want to script allocating access to the app rather than using the web interface. App show up at https://myapps.microsoft.com

Azure AD Premium is required for group access which would be ideal, but if you don’t have that you’ll need to add access on a user by user basis.

Answer:

PowerShell of course. First, you’ll need Azure AD for PowerShell (Preview version 2.0.0.17 at time of writing).

The below script which I modified from Philippe’s comment here should cover both internal, cloud and B2B invited users. The original script was using -objectid rather than -searchstring which works better and is more accurate for the internal and cloud accounts, but doesn’t work at all for B2B accounts.

The AppID can be obtained from this command:

Get-AzureADApplication -SearchString “Display Name for App”

Put the corresponding AppID into the below script, and you’re good to go. You’ll get prompted for Azure AD credentials as per usual. You can also get this

This is designed for a single user addition, but you could easily import the email addresses from a CSV file, and do a ‘for each’ on each entry like I did here.

# The UserPrincipalName or ObjectId of the user
$userId = “email@contoso.com”

# The AppId (a.k.a. “client ID”) of the app to assign the user to
$appId = “AppIDGoesHere”

# Connect to Azure AD
Connect-AzureAD -Confirm

# Get the user to be added
$user = Get-AzureADUser -searchstring $userId

# Get the service principal for the app you would like to assign the user to
$servicePrincipal = Get-AzureADServicePrincipal -Filter “appId eq ‘$appId'”

# Create the app role assignment
new-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId -PrincipalId $user.ObjectId -ResourceId $servicePrincipal.ObjectId -Id ([Guid]::Empty)

 

Note: If you try this and get the error below, it’s because the app is already assigned.

new-AzureADUserAppRoleAssignment : Error occurred while executing NewUserAppRoleAssignment
StatusCode: BadRequest
ErrorCode: Request_BadRequest
Message: One or more properties are invalid.
At Z:\script.ps1:17 char:1
+ new-AzureADUserAppRoleAssignment -ObjectId $user.ObjectId `
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [New-AzureADUserAppRoleAssignment], ApiException
+ FullyQualifiedErrorId : Microsoft.Open.AzureAD16.Client.ApiException,Microsoft.Open.AzureAD16.PowerShell.NewUser
AppRoleAssignment

Softerra Adaxes – Several Months In

logo-1

Softerra Adaxes is an Active Directory management & automation tool which I’ve grown very fond of.

First I reviewed Softerra Adaxes, then I actually bought Softera Adaxes and even did a brief case study for them. I thought it would now be good to share how far we’ve come through using this tool, and what the experience is like for those considering this option of automation. Here’s my thought process and how I personally approached the rollout, along with my experiences along the way:

Initially to me, the idea of having an ‘Outlook rules’ style approach to building a system that automated user management was enough to me. We’d been creating accounts manually for a long time, and the process was documented but took 20 minutes or so to perform. There was also a lot of room for human error, especially when someone was interrupted while creating an account.

There was of course the ‘selfish’ reason of not wanting to do these user management tasks myself, but it’s hard to pass those tasks off with the inherit risks or lack of knowledge of the tools being used to ease the process. This is what had held me off writing my own giant PowerShell script to automate all the steps.

After mucking around with the Adaxes basics, I started to realise that this software solution seemed to actually deliver on what I was personally looking for – something that wasn’t complex, but also let me define whatever criteria, business rules and caveats to the user creation process that I wanted. On top of that, there was inbuilt webpages where I could deliver these options to other staff requiring no software installs, and the ability to show or lock down whatever I chose, to both control and protect the Active Directory environment.

It did take a few weeks to set up properly, but I wouldn’t have really spent more than an actual day’s worth of work in those few weeks doing it. That was just to create a new user in all the various systems I wanted, with our unique user setting requirements. I wouldn’t say the entire system is so simple and easy to navigate that you can get cracking, but it’s also not complex. Once you find the setting or understand how Adaxes achieves a solution, it’s not difficult to set things up.

The inbuilt functionality of website templates – where you can create multiple sites displaying whichever fields you like to whichever users you like – is a good way to deliver the solution to end users. You can have a page for IT and another page for Finance with completely separate functions to best fit each use case.

For me, it was great that I could create websites with zero programming requirements. It’s all driven by a GUI, and somehow it’s still very flexible in what it can do. It might be frustrating to someone who actually writes code, but that’s not who would normally be using this solution. I really feel it’s aimed at someone like me, the IT Pro/Sys Admin who wants to automate and allow others to use the tools, without needing to code or expect others to run PowerShell commands themselves.

adaxes1Basic site with one option – menu and right side options can even be hidden if required.

Once I’d finished the user creation process and published the method of doing so to a website, I had internal staff muck around with it and use it, purely for new user creations. The feedback I received was immediately positive – that 20 minute or so process had been reduced to a few minutes, and even generated out an email saying the account creation was done. This in itself to me was the tick of a successful project, and I knew I could do a lot more around automation and empowering others to do repeatable tasks.

Some of the problems I hit on the user creation automation were:

  • After upgrading from Lync 2010 to Skype for Business 2015, there were intermittent errors popping up for creating a SfB user. This was a known problem to Softerra, and took several months to resolve with a new version of Adaxes. I did have a workaround luckily, so it only took some rule modifying to work around it until a proper solution was found.
  • ‘User unknown’ – I ran into some problems where I’d create the user or enable them for Exchange, but then the next command wouldn’t find the account. Adaxes was faster than what other systems could replicate changes, so some tactful ‘start-sleep’ PowerShell command steps during the workflows to allow replication to occur before the next step triggered. This does mean that the overall process can take a minute or two, and the person who triggered the user creation has to wait for it to finish.
  • Not all functionality was available that I needed in the GUI. For example, creating a Skype for Business user is easy, but you can’t assign a policy. Instead you need to use PowerShell commands to do what you want. That took a bit longer and needed more testing, but wasn’t much of an issue once I found that out.
  • When a new user was created that already existed (e.g. another John Smith – john.smith) I hadn’t considered that scenario. I asked in the Adaxes forums and was told how to run some pre-checks to make sure the username and phone number were unique and bomb out if they weren’t, rather than half creating an account and having to clean it up afterwards.
  • The upgrade process isn’t painful when a new version of Adaxes comes out (which came out while I was doing the user creation and I wanted to try upgrading early on), but there’s a few more steps than next, next finish. An uninstall is required with backing up a few files, then a fresh install and importing what you backed up. I’m hoping that will be streamlined a bit in the future.

After the user creation process was settled, I started to create more automation tasks. Deprovisioning was an obvious one, and was a lot easier than user creation as well as taking a lot less time to set up. This command would clean up all the bits and pieces from an account, including home drives and Exchange settings (along with moving the mailbox to a different database). This was rolled out relatively quickly.

I should also note, the logging is very helpful. If someone triggers a command from the website, they can see if it was successful or not, or where it failed. It made testing easy to do, but I was also able to read through logs via the GUI on the server to find out more about what failed and why.

adaxes2Updating options on one of the web interfaces – no coding required.

I then decided to wait for common scenarios to come up and build them as needed. We often had ‘returning staff’ which if their Active Directory account still existed, I couldn’t use my user creation method when the account already exists. This took a rethink of how I’d designed my rules so far, and decided to re-do a lot of it in a more modular fashion. Because there’s the ability to copy and paste rules, this was a lot easier than I expected. The end result was that I’d have a list of modules to run against a task – e.,g. a new user would call commands such as ‘enable email’ and ‘enable Skype for Business’ which my new ‘returning staff’ would call ‘re-enable email’ but the same ‘enable Skype for Business’ command as a new user. This now meant I could move a mailbox from one database to another and unhide the user from the Global Address Book when they returned, but because all users have their Skype for Business disabled, that step was the same in either scenario.

Another valuable idea I had was to let users control the membership of Active Directory groups that they were the owner of. After some mucking around, I created a website solely for that purpose. The great part about it was that whomever logged onto the site (with passthrough authentication so no extra typing required) could only see groups they were an owner of, based on the Manager field in Active Directory. This gives anyone in the company who is in control of a group, the ability to add or remove members without any IT assistance required. Perfect for application owners who control who can get to their application or not via a security group.

My next task will be the automation of a user name change. With the updated modular design, I can copy out the steps that I need and modify them to my new requirements; of course finding the hour or two to build and test this is the hardest part. (Note: Between the week of writing this and publishing, I’ve now done it.

I’ll give praise to both the Adaxes forums and their helpdesk support via email- almost always, within 24 hours max (and usually 4-5 hours) I’d get a specific and clear answer on how to do something I couldn’t work out personally, and it was from someone who knew the product rather than a basic 1st level helpdesk type response.

I hope this gives a real impression of my experience and opinion of Softera Adaxes at a high level, after using it for an extended time. There’s no real gaps to the product that I’ve found. and you can pick and choose as to how much customisation you want to do through PowerShell scripting. I’m still happy with the product, and it will continue to evolve with us.

Fix Wrong Domain for Users Azure Active Directory

I ran into a problem where a user couldn’t sign into Intune, which uses Azure Active Directory to authenticate users.

After checking the user in question on the Azure Active Directory portal, I noticed the domain was wrong:

aad

The user was being synced from On Premise Active Directory, so I had a look via Users and Computers to see what was going on. The user’s User Principal Name domain field was set differently to other users – instead of the proper mydomain.com, it was set to mydomain.local – another valid internal domain to Active Directory, but not one that Azure Active Directory knew about:

aad2

The unknown domain caused Azure Active Directory to disregard it, and instead use it’s default tennancy domain of wrong.onmicrosoft.com. I thought just changing the dropdown menu to mydomain.com instead of mydomain.local would fix it, but a forced Azure Active Directory Sync sync reported the change was successfully synced, but didn’t actually change the value.

I’m going to guess this is by design, as you don’t usually want logins changing. There is an easy way to change the via PowerShell instead.

Once you’ve run the standard ‘Connect-MsolLService‘ cmdlet, you can use ‘Set-MsolUserPrincipalName‘ to change the user. The full command is:

Set-MSolUserPrincipalName – userprincipalname “existinguser@mydomain.local” -NewUserPrincipalName “existinguser@mydomain.com”

Pretty simple, and the change is immediate.

I then realised there may be other users with the same problem, so dediced to use the Active Directory PowerShell Module with this command:

get-aduser -filter * | where {$_.userprincipalname -like “*local*” -and $_.enabled -eq “true”} | select name

This showed all the users who had ‘local’ in their UPN. As there were only a few, I changed them all one by one with the first command above.

The same check can be run against Azure Active Directory users with this command:

get-msoluser -all | where userprincipalname -like “*local*”

Easy!

Softerra Adaxes Identity and Active Directory Management Review

I’ve been asked to review many products (both hardware and software) on this blog. Many of the things I write about here are triggered by my experiences, which I think adds to the usefulness of the posts. Usually I decline, because I either don’t have an interest in the product, or don’t have the time to invest reviewing something that I can’t get a personal benefit out of the product in question.

Softerra Adaxes was one of these companies. After giving it a quick once over, my interest had been piqued. After extensive testing, I was actually happy to write a review of what the product does, and how I can see it helping people in businesses… so here is my take on the product. This is a sponsored post, but written by myself with my honest view on the product after extensive testing.

What is Softerra Adaxes?

First and foremost, this is an Active Directory (AD) Identity Management piece of software. It will talk to your AD environment (don’t worry, no schema changes required!) and give you a framework to allow automation. I’d previously looked at System Center Orchestrator (SCOrch) to look at the automation of user accounts such as creation, change, deletion – but it was too complicated for my liking. Most things required you to write your own code (PowerShell, .NET etc) and use what I’d call strange variable calls, instead of plain old nice code. To me, you have to wear a developer hat to use SCOrch for anything beyond very basic workflows.

Adaxes takes a different approach. Instead of writing your own code (which you can do still), much of it is driven in a similar way to how Outlook rules work. You can use the Adaxes Console, or Adaxes webpage to perform tasks such as ‘Create User’ – but you define the rules. For example, think of the ‘City’ field in AD. These are the rules you can set for it:

adaxes1
Those rules then end up as the only choices via a drop-down menu:
adaxes2

Having a default value if > 50% of your users are going to be in a particular city is a time saver. Same applies to being able to list several cities, and have a dropdown list to select them from – removing human error from typos. Forcing the property to being required also means it won’t be missed. To me, this gives immediate benefit in the user creation process, if the time is spent setting it up correctly.

User Creation in Adaxes

Once a user is created with your template, ‘Business Rules’ can kick in. These are more rules based on an event happening – such as a successful user creation. For me, I created business rules based on the City. If they’re in Sydney, then do all these things that applies to a Sydney person. This can be the creation of a home drive, but also can hook into Exchange or Lync to create their account in that environment too.

adaxes3

The Exchange and Lync integration allow you to have a user fully set up without even needing to worry about it. The email alias can be pulled from the username, and normal email address policies apply for creation of SMTP addresses. You can specify which DAG the mailbox will be created on too. For Lync, it’s the same story. If you’re lucky enough to have Enterprise Voice, the user’s phone number can be used as a variable to create a Line URI for the user.

Other third party systems can be manipulated by running a PowerShell script or program easily enough, or if you want to start getting tricky… there’s the Adaxes SDK for API.

When it’s all done, you can even trigger an email to alert staff that a user has been created, which could be used to alert other departments of any manual processes they need to do once a user is ‘born’.

Even better, is the easy built-in security roles. You can give HR access to create a user via the native Adaxes web page. No software required, HR follow the bouncing ball of the webpage and see a prompt for any required field, and requests can be configured to require approval before being actioned too.

What Else Can Adaxes Do?

I’ve focused on User Creation so far, because that was the first benefit I saw from Adaxes – but there’s a bunch more this software solution can do. Softerra themselves list many of the features of the product, but it’s a very open framework where you can make the software do what you need to happen.

  • Group Management

Due to the granular security model they use, you could consider end user management of groups. Email group management for end users is already possible from Microsoft Exchange, but you can’t do the same with security groups. I can see a big benefit in letting key users manage a selection of security groups which could allow things such as access to network drives and folders, access to software or permissions to an internal resource such as a SharePoint site and so on. If you’re in a Microsoft environment, everything should be security based via AD groups anyway, so this is a much nicer solution than giving those key end users an Active Directory User and Computers console.

  • Password Expiration Notifcation

There are several built in examples of ‘Scheduled Tasks’ – including some I’ve written my own script for! The ‘Password Expiration Notifier’ does exactly what I wrote here, which is to notify end users via email when they have certain days left before their password expires. My preference is to have all of these tools and triggers in a central location where all the right people can see what’s going on with ease, which is better than having Windows based scheduled tasks scattered around your servers being harder to find and manage.

password

Although I encourage everyone to know PowerShell, the reality is we all have different skills and priorities. Having middle-ware that manages the smarts, and shows you in an easily readable format reduces company risk in both managing automation as well as staff time in making changes should be at least investigated for it’s potential value. The above example out of the box had only the 7 day notification, so I copied and pasted the rules below it, and set the trigger to also happen at 1 day, matching my script. That was 10 seconds of work.

adaxes4

  • Clean Up Old Computer Records

Another example of a built in Scheduled Task is the ‘Inactive Computer Deleter’. Simply, it does a daily check for computer objects to see if they’ve been inactive for more than 12 weeks. If true, it changes the ‘When Marked Inactive’ property of the computer to the current date and time. It won’t delete the computer until it has approval, and you can tell it who to get the approval from. Tasks like this should save you time as well as helping to secure your network from rogue devices.

  • Office 365 User Management

There is also Office 365 support, which can automate tasks such as user creation, or license management. At the time of writing, an Office 365 CAL can’t be auto assigned to an Office 365 user when synced from Active Directory, but Adaxes can automate that step for you.

Conclusion

To me, the above is enough of a business case to at least consider Softerra Adaxes. Some time needs to be invested to make the software do what you want to do – every businesses’ user management processes are different. If you’re currently using just a PowerShell script, you could use that from Adaxes and build the workflow and web interface management around it for starters, then migrate tasks to Adaxes as you find time.

I can’t find many weaknesses in this solution – there’s provision for resiliency by having more than one server, the product seems secure and stable. I would like to see more built in options on what you can do out of the box (to Softerra’s credit, there is a lot of options already and is highly configurable). I noticed that I couldn’t specify some extra parameters in Lync beyond the basics of user creation, such as which policies to apply to a user. This will have to be done by calling a PowerShell script I’d write instead.

There’s also a bit of a learning curve around applying security and using the interface – not that it’s difficult, and the online documentation is extensive, but you’ll need to do a bit of tutorial reading to understand the product and how to configure it to your liking.

I also really like the potential of giving end users control over certain things. Empowering users that make decisions to act on those decisions themselves is a time saver – as is having an incredibly easy workflow approval process that doesn’t need a complicated workflow engine and a team of developers behind the scenes.

Overall, I really liked the product and the direction they have taken it. I personally recommend checking it out, and am actually in the process of implementing it in my current workplace as a result of this review, as a paid product!

 

Other Adaxes videos are available on YouTube, along with pricing available on their website (there’s also a 30 day trial – install is very simple).

Security Group Management Script

Over at eNow Consulting’s blog, I submitted an article and script on Exchange Group Management. It’s been working great for me, and hopefully will help others. I had a similar requirement around Security Groups, and this is the result.

The script itself is almost identical, but I wanted to share it anyway. I think it’s a great demonstration that you can really customise a script for whatever purpose you have. If you want to know how the script works generally, read my post at eNow, but there’s only one line different.

Instead of creating a “New Distribution Group”, it’s creating a New AD Group. The whole command is a bit different in syntax, but it’s still doing the same thing – creating a group. If you only wanted to manage existing groups, and removed the line altogether, you could manage both email and security groups from the single script (assuming a since csv file contains everything you want).

Here’s the script:

# Script to populate members of Security Groups
Start-Transcript -path C:\Scripts\Admin\Logs\securitygroups.txt
$data = import-csv C:\Scripts\Admin\securitygroups.csv
foreach ($group in $data){
New-ADGroup -name $group.GroupName -GroupCategory Security -GroupScope Universal -Path “OU=Security Groups,DC=mydomain,DC=com,DC=au” -Description “Automatically Managed by  @AdamFowler_IT’s Script”
$users = Get-ADUser -SearchBase “ou=Users,dc=mydomain,dc=com,dc=au” -Filter $group.filter
Get-ADGroup -Identity $group.groupname | Set-ADObject -clear member
Add-ADGroupMember -Identity $group.groupname -Members $users
}
Stop-Transcript

Ideally, you should intelligently create security groups based on criteria around how the business functions. For example, the Finance department can have their own security group, if their department is Finance. Makes sense right?

The catch though, is to NOT link any actual security to this group. You don’t want 30 different things (e.g. files, folders, sharepoint sites, anything you’d use a security group for) pointing to one group. What if the Finance folder needs to be accessed by the CEO of your company? You shouldn’t just add them to the group by adjusting the filter, because they’ll get access to the 29 OTHER things pointed at this group.

The way around this is to have a security group for every single separate thing you apply security to. Have a Finance drive? Then create an AD security group with a descriptive name, and then add the original Finance security group as a member. This way, if someone joins or leaves the Finance team, security will automatically apply. On top of that, if you need to give the CEO access to the Finance drive by this secondary group, knowing you’re only giving them access to that one thing.

One to one relationships on a security group and what it applies to, will make managing it in the future much easier. You could extend this even further, and have a security group for each job function – this would mean there is a CEO security group that contains the CEO, and you can then add that security group to anything they need. The biggest benefit of this is when your CEO quits and another one comes along, you can just add the new CEO to the CEO group and they’ll get the same access. Not sure what access the CEO gets? Check what the CEO security group is a member of, and all your smartly named security groups will be listed.

My last tip around security groups is to note down who’s in charge of the group in either the notes or description field. If a query comes up a year later, you may not remember who originally asked for the security. Having a person or a job title listed means you can quickly get approval for making membership changes to the group.

Thinking about how you’re going to manage things in the future and planning around it might be a bit more painful at the time, but it really pays off in the end.

Updating Active Directory from a CSV

Scenario:

You’ve been asked to populate everyone’s Active Directory job title. The payroll system is correct, and they’re able to export you a list of usernames and correct job titles. All you need to do is get that into AD.

Solution:

You could do this manually of course, but that’s no fun and a waste of time. This is one of those scenarios where you’ll hopefully think ‘PowerShell can do this!’ and possibly wonder how. That’s what I did anyway, so set out to make it work.

Here’s a fake example of the data I was working with, in a file called fake.csv:

EMPLOYEENAME,JOBTITLE
AFOWLER,IT OPERATIONS MANAGER
RSOLE,JANITOR

Tip: If you open a csv file in Excel it is a bit easier to read.

From this data, we want to match the EMPLOYEENAME to the correct AD account, then update the Job Title field from the JOBTITLE entry of the csv file.

A script that will do this is:

Import-module ActiveDirectory
$data = import-csv -path c:\fake.csv
foreach ($user in $data){
Get-ADUser -Filter “SamAccountName -eq ‘$($user.employeename)'” | Set-ADUser -Replace @{title = “$($user.Job Title)”}
}

So, what’s happening here? It can take a bit to get your head around especially if you’re not used to programming (like me), so I’ll try to explain it:

Import-module ActiveDirectory
Importing the ActiveDirectory module so the Get-ADUser command works. If you can’t load the module, install RSAT (Remote Server Administration Tools) which includes the AD module.

$data = import-csv -path c:\fake.csv
This is setting the $data variable to memory, which will contain all the contents of the fake.csv file.

foreach ($user in $data){
This is saying ‘for each line of information from the $data variable (which is the csv file), map that to $user and do the following”

Get-ADUser -Filter “SamAccountName -eq ‘$($user.employeename)'” | Set-ADUser -Replace @{title = “$($user.Job Title)”}
This is getting any AD User where their SamAccountName matches the employeename column of the $user variable (which is the current line of information from the csv at time of processing). Then with the pipe | it will use the result to then Set the AD User’s title field (where the job title goes) to the Job Title part of our $user variable. This command will run twice, because there are two lines for ‘foreach’ to process.

}
This is closing off the command which each ‘foreach’ command runs.

 

I hope that explains it enough so you’re able to manipulate the script to your own requirements.