Powershell

Deploying a Locked Down Start Menu in Windows 10

The tiles in Windows 10’s Start Menu can be rather messy. By default, you get a lot – and they may be things you don’t want there such as News, Sports, Photos, Microsoft Store etc.

Since Windows 10 1607, there’s been a way to control this. Customize Windows 10 Start and taskbar with Group Policy covers how to do this, but there’s some errors and links that don’t work, so I thought it was worth giving a quick overview on how to do this.

Keep in mind that this process locks down the Start Menu tiles completely, users won’t be able to add, remove or change anything to do with tiles.

The first step is to configure the Start Menu tiles how you want them on a computer. You can add, remove, move, resize etc until you’re happy with how it looks.

Once that’s done, you’ll need to export the layout to an XML file. Easily done by opening PowerShell and running Export-StartLayout. This needs the -Path switch, e.g. Export-StartLayout -Path “C:\temp\startmenu.xml”

Copy the resulting startmenu.xml file into a central location that clients will be able to access, or copy it out to each machine through Group Policy Preferences. This XML file will be called in the Group Policy setting “Start Layout”.

The Group Policy setting called “Start Layout” lives in User Configuration or Computer Configuration > Policies> > Administrative Templates >Start Menu and Taskbar. You’ll probably want this at the user level rather than the computer level, but it depends at what layer you want this locked down at. 

If you can’t see this policy at all, then you may need to update your Group Policy templates. Each time a new version of Windows 10 comes out, there’s usually new or updated Group Policies to use. There’s a good step-by-step here if you need help – I’d recommend downloading the templates that match the latest version of Windows 10 you’re managing.

Start Layout in Group Policy

For this policy, you’ll be setting the radio button to Enabled, and setting the Start Layout File value to the path of the XML file that you copied out or placed centrally.

Start Layout Settings

Once that is done, the Group Policy object containing this setting needs to be pointed at the users or computers you want it to apply to, just like any other Group Policy.

The end result is the client then having the same Start Menu tiles configured in the XML file.

You may find that some of the tiles are missing. I’ve seen this happen when the shortcut the XML points to isn’t in the location expected. Here’s an example XML file with just one tile configured for Notepad:

<LayoutModificationTemplate xmlns:defaultlayout="http://schemas.microsoft.com/Start/2014/FullDefaultLayout" xmlns:start="http://schemas.microsoft.com/Start/2014/StartLayout" Version="1" xmlns="http://schemas.microsoft.com/Start/2014/LayoutModification">
 <LayoutOptions StartTileGroupCellWidth="6" />
 <DefaultLayoutOverride>
 <StartLayoutCollection>
 <defaultlayout:StartLayout GroupCellWidth="6">
 <start:Group Name="">
 <start:DesktopApplicationTile Size="2x2" Column="0" Row="0" DesktopApplicationLinkPath="%APPDATA%\Microsoft\Windows\Start Menu\Programs\Notepad.lnk" />
 </start:Group>
 </defaultlayout:StartLayout>
 </StartLayoutCollection>
 </DefaultLayoutOverride>
</LayoutModificationTemplate>

When a Tile is added to the Start Menu, if it doesn’t exist already, it will create a .LNK file and uses that for the tile. You may need to copy these off the computer you created the tiles on the in first place too, and copy them out to the same path on the computers you’re pushing this setting to.

You can also manually update or change the XML file yourself, which can sometimes be easier than going through the whole export process again.

One last thought I have on this, is that you can have multiple XML files going to different computers or users based on their requirements – but don’t over complicate things or you’ll be constantly managing tiles!

Exchange Online and Exchange On-Premises Security Groups

I had a requirement come up where I needed a list of Exchange Online users as well as Exchange On-Premises users. If you’re hybrid or going through a long migration, there’s many scenarios where you want certain products or settings applied against the users and accounts based on where their mailbox is.

You could maintain that manually, but why do that when you can automate it!

Thankfully there’s two easy commands that will return accounts based on where they are – get-mailbox and get-remotemailbox. As long as you’re in Exchange Hybrid mode, you can run both of these commands against your local Exchange server.

Here’s the quick script I wrote up:

$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://exchange server name/Powershell -Authentication Kerberos
Import-PSSession $session
$remoteusers = Get-RemoteMailbox -resultsize unlimited
$onpremusers = Get-mailbox -resultsize unlimited
$remotegroup = "Exchange Online Users"
$onpremgroup = "Exchange OnPrem Users"

Get-ADGroup $remotegroup | Set-ADObject -clear member
Get-ADGroup $onpremgroup | Set-ADObject -clear member

foreach ($user in $remoteusers){
Add-ADGroupMember -Identity $remotegroup -Members $user.SamAccountName
}

foreach ($user in $onpremusers){
Add-ADGroupMember -Identity $onpremgroup -Members $user.SamAccountName
}

Remove-PSSession $Session

All I’m doing here is getting the two user types and putting them into variables – $remoteusers and $onpremusers. I’ve also used the group names as variables, so you can create whatever AD groups you like and put them into the $remotegroup and $onpremgroup variables.

Also I’m clearing out all members of the group before adding users, which means if someone gets migrated or leaves, they’ll be cleaned up from the old group properly.

Finally I’m going through the two users lists and using ‘foreach’ to add each user to the relevant group, using the SamAccountName value of each result to identify them.

It’s only a basic script, but you can easily add extra filters to the Get-Mailbox and Get-RemoteMailbox commands to do things such as only getting users from certain OUs.

This was tested on Exchange 2010, so if you get different results on Exchange 2013 or 2016 please let me know.

Recover a SharePoint Online Site

In SharePoint Online, it’s easy to delete an entire site or documents in a site.

Recovering documents is also quite easy, go to the site’s recycle bin which is normally located at https://contoso.sharepoint.com/sites/sitename/_layouts/15/RecycleBin.aspx – replacing ‘contoso’ with your tenant name, and ‘sitename’ with the actual site name. Deleted items can be selected and restored to their original location.

How long are deleted items kept in the Recycle Bin?

In SharePoint Online, the default retention time is 93 days for both site recycle bin (first stage) and site collection recycle bin (second stage). The site recycle bin storage counts against your site collection storage quota and the List View Threshold. The site collection recycle bin retention starts at the same time for both recycle bins when the item is first deleted, so the total maximum retention time is 93 days for both recycle bins. The default amount of space for the site collection is 200% of the site collection quota.”

However, I had some issues when trying to recover an entire site. My top level recycle bin https://contoso.sharepoint.com/_layouts/15/RecycleBin.aspx showed nothing. I’m unsure if there’s another way of viewing deleted sites via the web interface, and gave up after a lot of clicking around and Googling, but it’s easy to do with PowerShell.

After installing the SharePoint Online Management Shell, and connecting to SharePoint Online with the ‘Connect-SPOService‘ cmdlet (and don’t forget to use HTTPS rather than HTTP when connecting to your SharePoint Online instance or you’ll get a rather generic error: connect-sposervice : Could not authenticate to SharePoint Online http://contoso-admin.sharepoint.com/ using OAuth 2.0), you can see what your deleted sites are with this command:

Get-SPODeletedSite

Simple, you’ll then be presented with a list of all sites that are deleted and waiting in the recycle bin along with when they were deleted, and how many days are remaining before they disappear from the recycle bin. Sites deleted seem to sit in that recycle bin for 30 days, rather than site collection items but I couldn’t find any documentation supporting this.

To restore a deleted site, just use the following command with the URL of the site to restore, which you can see from the ‘get’ command above

Restore-SPODeletedSite -identity https://conotos.sharepoint.com/sites/oops

That’s it. Your site is back.

From what I’ve read, there is no way to change the retention values of SharePoint Online recycle bins.

Updating the Country Field in Active Directory

Wanting to have all users to have the country ‘Australia’ in Active Directory, I thought it would be a simple PowerShell command. Get all the users you want and set a field to ‘Australia’. However, it’s more complicated than that.

As you can see from the above, the Country/region field is a dropdown, where you can select the country. If you look in PowerShell using ‘get-aduser username -properties *’, there’s 4 fields that get populated with this setting:

c : AU
co : Australia
Country : AU
countrycode: 36

Trying to just change one of these fields will result in an error such as:

Set-ADUser : A positional parameter cannot be found that accepts argument ‘Au’.
Set-ADUser : A positional parameter cannot be found that accepts argument ‘Australia’.
Set-ADUser : A value for the attribute was not in the acceptable range of values

The answer is that all fields need to be set at the same time. The C and Country fields are based on ISO 3166 codes, with Australia being AU and 36.

The resulting command would end up being:

set-aduser adam.fowler -Replace @{c="AU";co="Australia";countrycode=36}

Of course this can be done on a boarder scale by using ‘get-user’ with a larger scope, and piping that into the set-aduser command:

get-aduser -filter "company -eq 'Contoso'" | foreach {set-aduser $_ -Replace @{c="AU";co="Australia";countrycode=36}}

That’s all that’s required to change the field.

Checking CSV Against Active Directory Users

I’ve written before on how to update Active Directory from a CSV. This time, I’ve got a CSV list of users that I want to check are valid users against my Active Directory (AD) environment.

There’s a huge amount of ways this can be done, and this is just one of them. If you have others, or ways to improve this I’m always keen to hear!

This script assumes you have a CSV file with the header (first line) with the word ‘users’. Here’s an example CSV file: myusers.csv

Below is the PowerShell script I wrote. I’ve also written about ‘If’ and ‘Else’ before, so read that if you want some clarification. The user list I have is based on User Principal Name (UPN) rather than just username, so I’m searching AD to see if there’s a match or not.

Import-Module ActiveDirectory

$Data = Import-Csv myusers.csv

foreach ($user in $data){
$upn = $user.user
$check = $(try {get-aduser -filter "userprincipalname -eq '$upn'"} catch {$null})
if ($check -ne $null) { }
else { "$upn Doesn't Exist" }
}

What I’m doing here is setting each line of the CSV as the $UPN variable to search for. Then using the ‘Try‘ function, I’m catching if there is no result/match (null). If there’s a match, it won’t equal null, so display nothing. Else, show the UPN via the $UPN variable and follow that with ‘Doesn’t Exit’.

This way, I will only get results back from each AD search where the UPN in the CSV doesn’t match a user’s UPN in my AD environment – and I get to see what those results are.

This script method can be applied in many different ways of course, but it was the first time I’d used the Try function, and it worked really well.