Skip Ribbon Commands
Skip to main content
 

 Follow Me

 
 
 
 
 

 SharePoint Blog List

 
  
  
  
  
  
  
  
  
  
  
  
  
Todd Klindt's home page > Todd Klindt's Office 365 Admin Blog
What's going on with TK.
January 09
Podcast 417 - Expectation Failed
January 08
Podcast 416 - 30 Second Skip

In this episode, the guys spend a good bit of time talking about PowerShell instead of Todd's New Year's vacation. Almost shocking how well they stayed on topic. They also discuss WeMos, how it took Shane four hours to get it working, and how Shane feels it wasn't his fault but the wireless's fault. Updating Office 365 to Monthly update cadence, Surface Laptop 2, and Todd's love of OneDrive round out the show. Not too bad and no stupid New Year's resolutions so you can safely listen.

Audio File

Video File

Podcast 416 - 30 Second Skip (Time 0_34_58;03)

YouTube (Subscribe)

Subscribe in iTunes

Running Time: 54:23

Links:

Pause Syncing with PowerShell
Help me pause OneDrive Sync with PowerShell
Change your Office update cadence
How to Enable Windows Sandbox
WeMo Mini Smart Plug
PowerApps and Flow Training
SPTechCon Austin 2019
North American Collab Summit
SharePoint Saturday Omaha
SharePoint Conference NA

ShortURL: https://www.toddklindt.com/Podcast416

January 04
Changing Windows 10’s Language to English with PowerShell

I recently had to do some testing for a customer on a Windows 10 machine whose primary language was not English. I, having lived in the midwest United States my entire life, do not speak any other languages fluently. The customer anticipated this and sent along a very thorough set of directions, complete with pictures, of how to switch the language to English for me. Very kind of them indeed. As I looked through the directions the thought occurred to me, “I’ll probably need to do this a few times. I wonder if I can automate it. I bet I can! PowerShell to the rescue!”

Here’s the TL;DR of what I ran:

Set-Culture en-US
Set-WinSystemLocale en-US
Set-WinHomeLocation 0xF4
Set-WinUILanguageOverride en-US

logoff

None of it takes place until you log back in, so I added the logoff at the end.

With PowerShell, knowing the answer is good, but knowing how to get the answer is even better. One of my friends, Jeff Hicks, always does a good job explaining the process of find things in PowerShell, so I thought I’d do that here.

I had the steps, in UI form, so I didn’t need to figure that out. There were 4 things that needed to be changed. I just needed to figure out how to make those changes with PowerShell. I went to a PowerShell prompt and typed:

Get-Help language

hoping PowerShell could nudge m in the right direction. I got this back:

image

It wasn’t exactly what I needed, but it did show me there was a module, International, that was probably a good place to start. And knowing that I needed to change things, I was mostly interested in the Set cmdlets. There were also a couple of good help topics, about_Language_Keywords and about_Language_Modes that I could reference if I got stuck. My next step was to see what other cmdlets were in the International module. I did that with Get-Command -Module International. That filled in the blanks for me.

image

For the pieces that didn’t obviously connect to the pictures I had from the customer’s instruction I had some Get cmdlets above to poke around and try to match values. My next hour or so consisted of running the different Get cmdlets and figuring out where the current setting was so I could use the appropriate Set cmdlet to change it. I made liberal use of the help cmdletname –examples paradigm to figure out what the parameter values should be.

After I was finished and was searching to see if I was right I found this Technet article,

How to change display language in Windows 10, that confirmed what I was doing was correct. My script is laser focused on what I was doing, that one is more useful and generic. If you want to understand this more, read through that script.

That’s all there was to it. I can’t reboot the client machine but all of these changes have persisted multiple logouts. I did also have to use the CHCP command to change the code page of the PowerShell window. That did not persist across logins. I think there’s a way to set the default code page in the Registry, but I haven’t chased that down yet.

tk

ShortURL: https://www.toddklindt.com/PoshChangeLanguage

January 02
Help me pause OneDrive Sync with PowerShell

OneDrive and PowerShell. They remind me of that song, My Favorite Things. Though I’m not sure which I like more, PowerShell or whiskers on kittens. It’s a close call.

I write a lot of little PowerShell scripts to help me get through the day, and often those scripts fiddle with files that are being synced with OneDrive. In a few cases, if the file is large enough, my PowerShell script will fail because OneDrive will lock the file while it’s syncing it. Both responses are totally understandable. I want PowerShell to stop if it can’t sync a file, and I want OneDrive to sync files when I expect it to. I could, and I have in some cases, written a bunch of PowerShell to check the lock status of a file, to deal with the locking issues that OneDrive creates, and so on. It’s tedious though, and honestly I’m not very good at it. That’s where you all come in…

What I really want, nay, need, is a way to pause OneDrive sync while I’m messing with files I know it will lock when syncing. I need two things, Stop-OneDriveSync and Start-OneDriveSync. That’s it. I asked Santa for them for Christmas, but all I got was socks.

So I took the next step, I whined on Twitter. Someone, suggested I create a UserVoice for it. I thought that was pretty solid advice, so I did.

Pause Syncing with PowerShell

What I’m asking from you, is if you too would like to be able to start and stop OneDrive syncing from PowerShell, please vote up the UserVoice. Here it is again in case you missed it. Smile

Pause Syncing with PowerShell

I’ll be keeping an eye on it, and I’ll know if you voted!

Thanks in advance.

tk

ShortURL: https://www.toddklindt.com/PoshPauseOneDrive

December 27
Podcast 415 - Brain Shut Off

Todd and Shane come back from the holidays and talk about some actual technical content for SharePoint Web Parts. Then they discuss some survey results for Teams and changes in licensing for parts of Office 365. They also talk about installing hardware with no drivers and some fun birthday charity updates.

Audio File

Video File

Podcast 415 - Brain Shut Off (Time 0_21_13;28)

YouTube (Subscribe)

Subscribe in iTunes

Running Time: 51:47

Links:

New SharePoint Web Parts
Teams passes up Slack
UPDATED - Updates to Microsoft Flow and PowerApps for Office 365
Information architecture in a flat SharePoint world
Package Thief vs. Glitter Bomb Trap
PowerApps and Flow Training
SPTechCon Austin 2019
North American Collab Summit
SharePoint Saturday Omaha
SharePoint Conference NA

ShortURL: https://www.toddklindt.com/Podcast415

December 27
Podcast 414 - Never Spend all of that Money

In this episode, even though Lori wasn’t there to supervise, the boys did a decent job of staying on topic. Maybe Lori is really the problem? In traditional fashion Todd starts off talking about a blog post he wrote on Office 365 group permissions and then some other fun about making SharePoint look nicer. Then Shane chimes in with his needs for a new laptop and reminds you how to fix SharePoint Workflow patches. Finally, they end with something interesting, talking about all of the events you can meet Shane at. A must listen!

Audio File

Video File

Podcast 414 - Never Spend all of that Money (Time 0_11_10;28)

YouTube (Subscribe)

Subscribe in iTunes

Running Time: 49:15

Links:

Copying Office 365 Group Permissions with PowerShell
New no code solutions for custom formatting in SharePoint
Modernize your SharePoint pages
Remove Background from Photos
PowerApps and Flow App in A Day
PowerApps and Flow Training
SPTechCon Austin 2019
North American Collab Summit
SharePoint Conference NA

ShortURL: https://www.toddklindt.com/Podcast414

December 18
Copying Office 365 Group Permissions with PowerShell

Office 365 Groups, or “Unified Groups” to their friends, have been a confusing Office 365 feature from an administrative perspective for as long as they’ve been around. Users can create them all willy-nilly (unless you disable that), the SharePoint sites they create don’t show up in the SharePoint Admin Center (until you use the new one), the list goes on and on. Another frustrating part of Groups is that you can’t do any nesting, of any kind. You can’t put a Unified Group in an Azure AD Security Group, or vice versa. You can’t put a SharePoint Group in a Unified Group either. Because of these limitations it’s very tough to reuse group membership. You can’t create an HR Azure AD Security Group and drop it into the HR Benefits Unified Group or the HR Hiring Unified Group. Now, the obvious question is, “If these two Unified Groups have the same membership why are they two groups instead of one?” Good question, fair reader. Sometimes it just works out that way. Sometimes the AD Security Group would only be a subset of the users in the Unified Group in question. Regardless, this question came up on a customer call and I was challenged to find a way to ease the administrative burden of manually adding the same users to multiple Unified Groups. What was my answer, “Of course I can do it, with PowerShell!” Sometimes I answer without thinking first.

I had used the PnP PowerShell in the past to get a list of all of the Unified Groups in a tenant, so I was pretty sure it could be done. I whipped out Get-Command and listed all the cmdlets that contained the noun “UnifiedGroup.”

Get-Command -Noun *unifiedgroup*

That exposed two cmdlets I’d need: Get-PnPUnifiedGroupOwners and Get-PnPUnifiedGroupMembers. That’s good. But there were no corresponding “Set” cmdlets. That’s bad. Normally this is where I’d panic. Instead, I took a deep breath and instead of panicking, I typed Get-Command -ParameterName members. Low and behold, Get-Command blessed me with New-PnPUnifiedGroup and Set-PnPUnifiedGroup. We retrieve the list of Members with Get-PnPUnifiedGroupMembers but we set them with Set-PnPUnifiedGroup –Members. Not totally consistent, but I can work with that.

From past experience I knew I had to be connected to the Microsoft Graph to use the Unified Group cmdlets, so  I authenticated against the Graph so I could start poking around.

Connect-PnPOnline –Graph

If you haven’t connected to the Graph before, hold onto your hats. It’s different than what you’re used to. You’ll be given a code to register with the Graph that allows the PnP to query it. It feels weird, but everything is working correctly.

Once I was connected to the Graph I ran Get-PnPUnifiedGroupMembers to see what the output looked like. It gave me back a collection of objects that corresponded to the users that were members of that Unified Group. Fair enough.

image

We can store them in a variable for use later. Now, how do we assign membership? help Set-PnPUnifiedGroup –Examples didn’t provide a lot of help. It only showed adding a single Owner. But it did show me that the cmdlet was expecting the parameter to be a string, and that string needed to be the UPN of the user I wanted to add. I can work with that too.

The mechanics of saving out the list of users and owners and assigning them to another group was a little tricky, but not bad. I walked through the output of Get- and wrote the UPNs of each object to a new collection that I would then pass on to Set-. It looks like this:

$members = Get-PnPUnifiedGroupMembers -Identity $source

$members | ForEach-Object -begin  {$memberlist  = @() } -process {$memberlist += $($_.UserPrincipalName) }

Set-PnPUnifiedGroup -Identity $destination -Members $memberlist -Owners $ownerlist

The only sticking point was that doing it that way would overwrite whatever Members or Owners the Destination Group already had. To handle that I created a boolean variable, $mergeusers, that I could set to $true or $false depending on whether I wanted to clobber the existing users or not. If I did not want to clobber them I used the following code to merge the current Members of Destination with the Members of Source:

$membersDest = Get-PnPUnifiedGroupMembers -Identity $destination

$members = $members + $membersDest

Then later when I write the users in $members to the Destination Group it has both sets of Members.

I added some very, very simple error handling so that another human has a chance of running it. This is what I ended up with:

# Set some values
# use Get-PnPUnifiedGroup to get Unified Group names
# Name of Unified Group whose owners and membership we want to copy
$source = "Regulations"

# Name of Unified Group whose owners and membership we want to populate
$destination = "Empty"

# Whether to overwrite Destination membership or merge them
$mergeusers = $false

# Check to see if PnP Module is loaded
$pnploaded = Get-Module SharePointPnPPowerShellOnline
if ($pnploaded -eq $false) {
    Write-Host "Please load the PnP PowerShell and run again"
    Write-Host "install-module SharePointPnPPowerShellOnline"
    break
    }
# PnP Module is loaded

# Check to see if user is connected to Microsoft Graph
try
{
    $owners = Get-PnPUnifiedGroupOwners -Identity $source
}
catch [System.InvalidOperationException]
{
    Write-Host "No connection to Microsoft Graph found"  -BackgroundColor Black -ForegroundColor Red
    Write-Host "No Azure AD connection, please connect first with Connect-PnPOnline -Graph" -BackgroundColor Black -ForegroundColor Red
break
}
catch [System.ArgumentNullException]
{
        Write-Host "Group not found"  -BackgroundColor Black -ForegroundColor Red
        Write-Host "Verify connection to Azure AD with Connect-PnPOnline -Graph" -BackgroundColor Black -ForegroundColor Red
        Write-Host "Use Get-PnPUnifiedGroup to get Unified Group names"  -BackgroundColor Black -ForegroundColor Red
        break
}
catch
{
    Write-Host "Some other error"   -BackgroundColor Black -ForegroundColor Red
break
}

$members = Get-PnPUnifiedGroupMembers -Identity $source

if ($mergeusers -eq $true) {
     # Get existing owners and members of Destination so that we can combine them
    $ownersDest = Get-PnPUnifiedGroupOwners -Identity $destination
    $membersDest = Get-PnPUnifiedGroupMembers -Identity $destination

    # Add the two lists together so we don't overwrite any existing owners or members in Destination
    $owners = $owners + $ownersDest
    $members = $members + $membersDest
    }

# Set the owners and members of Destination
$owners | ForEach-Object -begin  {$ownerlist  = @() } -process {$ownerlist += $($_.UserPrincipalName) }
$members | ForEach-Object -begin  {$memberlist  = @() } -process {$memberlist += $($_.UserPrincipalName) }

Set-PnPUnifiedGroup -Identity $destination -Members $memberlist -Owners $ownerlist

Feel free to pick through that for any nuggets that might help you.

Happy copying.

tk

ShortURL: https://www.toddklindt.com/PoshCopyO365GroupMembers

December 18
Podcast 413 - Payload is Too Big

In this week's episode, Todd tells us all about his European travel woes. Then he tells about some blog posts and patches that are happening. Shane tells about some training and videos he's made for PowerApps and Flow. They wrap up with some of the upcoming big events you'll want to attend.

Audio File

Video File

Podcast 413 - Payload is Too Big (Time 0_47_15;21)

YouTube (Subscribe)

Subscribe in iTunes

Running Time: 48:19

Links:

SysKit Security Manager
Bulk Undelete Files in Office 365 and SharePoint Online with PnP PowerShell
PowerApps Tooltip
PowerApps create a PDF from SharePoint List data
SPTechCon Austin 2019
North American Collab Summit
SharePoint Conference NA

ShortURL: https://www.toddklindt.com/Podcast413

December 11
Bulk Undelete Files in Office 365 and SharePoint Online with PnP PowerShell

Quite often as a consultant, you get to work on truly fun and funny situations. The customer email that prompted this blog post is one of them.

My contact at the customer site emailed with a problem. A few weeks earlier one of their users deleted some pictures from one of their document libraries. Okay, not a few pictures, nearly 100,000 of them. And not only had that user deleted nearly 100,000 items, they hadn’t told anyone for 3 weeks, while they tried to upload the pictures they still had locally. I know what you’re saying, this is a job for the Recycle Bin! And you’re right, it is, but the other factors made it complicated. They couldn’t just restore all of the files in the Recycle Bin as there were also 3 weeks’ worth of legitimately deleted documents in there, including documents from that user. Also, since the user had re-uploaded a bunch of the deleted documents there would be “overwrite” prompts all over that would slow down the process. And let’s not forget that even if it all went smoothly, someone would have to manually restore nearly 100,000 files. No easy feat.

The customer talked to me because they wanted to share the story (it was a big funny after the fact) but also because they knew I always said that PowerShell could do anything. They were hoping PowerShell could bale them out of this mess too. I don’t mean to spoil the end of this story, but they were right, it could.

My tool of choice when it comes to making magic in Office 365 is the PnP PowerShell. I had never done this before, so I had to do a bit of spelunking. I ran Get-Command *recycle* -Module SharePointPnPPowerShellOnline to see what PowerShell cmdlets availed themselves to me.  Looky, looky, there it is, Restore-PnPRecycleBinItem. Did you just hear angels sing? I know I did.

Now we needed to weave a little PowerShell magic. We couldn’t restore all of the deleted files, since other folks had legitimately deleted files in the meantime. We also only wanted to restore .JPG files that this user had deleted. Here’s command that got the files we needed:

Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg"  | Where-Object -Property Dirname -Like -Value "Shared Documents/*"  | Where-Object -Property DeletedByEmail -EQ –Value shane@tkdemo.com

That returned all the files, now what to do with them? Restore them, of course. This bit of code grabs all the files, counts them as it restores them, then spits out the time and date when it’s done.

$bin = Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg"  | Where-Object -Property Dirname -Like -Value "Shared Documents/*"  | Where-Object -Property DeletedByEmail -EQ –Value shane@tkdemo.com

$bin | foreach  -begin { $a = 0} -Process  {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }

Normally, that would have worked, but it was going to take some time to restore the files. A loooong time. Several days in fact. With some testing I surmised that the client PowerShell was the bottleneck. I used a little PowerShell trick to break the entire 100,000 item collection into chunks of 10,000 and run it multiple PowerShell windows and on multiple machines. I changed the second line to look something like this:

($bin[20001..30000]) | foreach  -begin { $a = 0} -Process  {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }

The $bin[20001..30000] only sends items 20001 to 30000 of the collection down the pipeline. I changed that on each client. $bin[0..10000], $bin[10001..20000], and so on. After they finished I ran through it all one more time to make sure I didn’t miss any. The entire script looked like this:

# Make sure necessary modules are installed
# PnP PowerShell to get access to Office 365
Install-Module SharePointPnPPowerShellOnline

# Module to securely store passwords
Install-Module CredentialManager

# Saved credentials
New-StoredCredential -Target "ImportantSite" -UserName
madowner@tkdemo.com -Password 'Password goes here' -Persist LocalMachine

# Now the actual meat
# Connect to the site collection where the files were deleted
Connect-PnPOnline -Url
https://tkdemo.sharepoint.com/ -Credentials ImportantSite

# Filter the recycle bin for only the files we want to restore, JPGs, from the Shared document library, deleted by Shane
$bin = Get-PnPRecycleBinItem | Where-Object -Property Leafname -Like -Value "*.jpg"  | Where-Object -Property Dirname -Like -Value "Shared Documents/*"  | Where-Object -Property DeletedByEmail -EQ -Value
shane@tkdemo.com

# See how many pictures Shane deleted.
$bin.count

# Walk through the collection and restore each document. Spit out the time at the end so you know how long it took. Also keep a counter to see how it’s going as it churns through the collection
$bin | foreach  -begin { $a = 0} -Process  {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }

# Since the OM is the bottleneck you can run this on multiple machines to speed things up. Here’s how to only restore a subset of the collection
($bin[20001..30000]) | foreach  -begin { $a = 0} -Process  {Write-Host "$a - $($_.LeafName)" ; $_ | Restore-PnPRecycleBinItem -Force ; $a++ } -End { Get-Date }

I hope you never need to use this. Smile

tk

ShortURL: https://www.toddklindt.com/PoshRestoreSPOFiles

December 10
Podcast 412 - Preconditioned Fail

Todd has left the podcast for the day and Shane brings in a guest. Troy from AppRiver. They discuss security and hacking. Troy talks about what kinds of things the bad guys are doing and what organizations and individuals can do to help protect against those things. Things even the technically challenged could do. Shane then tells us about community and upcoming events.

Audio File

Video File

Podcast 412 - Preconditioned Fail (Time 0_11_50;25)

YouTube (Subscribe)

Subscribe in iTunes

Running Time: 39:56

Links:

SysKit Security Manager
Information architecture in a flat SharePoint world
Creating PDFs from SharePoint Data
Online Free Flow Conference
Thrive Conference 2018
SP Fest Chicago
SPTechCon Austin 2019
North American Collab Summit
SharePoint Conference NA

ShortURL: https://www.toddklindt.com/Podcast412

1 - 10Next
 

 Subscribe to my Netcast

 

You can watch Shane and I's Cloudy Podcast live every Wednesday Morning at 10:00 am Central US time at http://www.toddklindt.com/Netcast

You can subscribe to the Podcast in the following ways:

MP3 Audio

Windows WMV video

YouTube Channel

 

 SPDocKit

 
June 2018
 

 Todd's Upcoming Events

 
  
  
There are no items to show in this view of the "Events" list.