Powershell

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 = $users.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.

 

Azure AD B2B PowerShell Invites

I’ve written about Azure AD B2B before, as well as then giving those invited users access to SharePoint Online, but there’s been a lot of changes since I started using it. Have a read of my original article if you’re interested to see how I’m using B2B and why.

Azure AD B2B is still in preview, but in Feb 2017 a bunch of improvements were added. Part of these changes were around using the new Azure portal rather than the Classic Portal, and with that is the removal of inviting users via CSV file and uploading it to Azure AD. This was exactly the way I was using it, so I had to change to one of the newer methods.

Although CSV support is gone, it’s been replaced by PowerShell which can just call the same CSV file being used before, so it’s not a huge change. There’s a PowerShell example on this technet page which shows how to do it. There is a catch though, the ability to add the user to groups as part of the import is gone.

The other big change that impacted me was the invitation emails. This is the email that gets sent to the recipient when being invited – it was originally a plain text email from a generic Microsoft address, but it’s now changed to a much more professional looking email. The catch with this is, rather than coming from a generic Microsoft email account, it now comes from the user that sends the invites out. I found this out the hard way when invited parties started seeing my details and photo with the invite!

There’s four approaches I can come up with around this new invite method –

1. Leave it as showing the admin user who does the invites (not ideal)

2. Create and use a seperate service account for these invites, so it comes from a generic looking internal email address (quite good)

3. Get the users themselves to send the invites out – by default, all users have access to invite others to their tenant (worst option, users won’t do this themselves, need training and support, can’t automate)

4. Use APIs and send the invites out on behalf of the user (‘best’ option but requires the most work, most complex)

While I look at option 4, option 2 is a good middle ground and will probably do for most companies.

I’ve written and tested the below script, which works on a single user by user basis. This uses just the Azure AD Preview module for PowerShell, which is at version 2.0.0.85 at the time of writing. To use the method mentioned on that page to install, I had to first install Windows Management Framework 5.0.

$group = get-azureadgroup -SearchString "Put your exact search string here" | where {$_.dirsyncenabled -eq $null}
$newuser = New-AzureADMSInvitation -InvitedUserEmailAddress emailaddress@contoso.com -InvitedUserDisplayName "Full Name" -sendinvitationmessage $true -InviteRedirectUrl "http://myapps.microsoft.com"
Add-AzureADGroupMember -objectid $group.objectid -RefObjectId $newuser.InvitedUser.Id

 

This script requires you to first authenticate against Azure AD with the command connect-azuread : the same way you’d use connect-msol for Office 365. More on how to automate that part in an upcoming blog post.

I’ve written this on the basis that you already have a group to add the guest user into, which gives them the permissions required after being invited into your Azure AD tenant. It’s also more a proof of concept script, which shows how to automate these steps enough to then be able to do what you want with it – such as wrap it around a ‘for each’ and feed multiple users into it.

The first thing the script does is get the group name. As objects in Azure AD don’t have to have unique names like on-prem Active Directory, this script will fail if it finds multiple results the same. It’s also making sure the result that comes back is only a cloud based group, because you can only add B2B invited users into Azure AD groups (not ones synced from on-prem).

Next it will send out the invite to the user. This is the important part. If you don’t want an email to go out, you can change the -sendinvitationmessage value to $false.

Finally we’re adding the invited user into the group by ObjectIDs of each object – straight forward.

—-

The end result is a user who will be able to accept their invite, log in and have access to whatever they need to. Note that the way I do this is by having an app and advertising it to the group that also gives permissions to SharePoint Online, so they’ll see the single link on their myapps.microsoft.com page.

If you’re mucking about with Azure AD B2B this should give you somewhere to start. The Microsoft Technet pages for Azure AD are very comprehensive now as well as being easy to read, so check them out.

If you have any questions on Azure AD B2B feel free to ask!

Update 23rd August 2017

I’ve now gotten around to making a mass invite script. I used Eric Schrader’s script, and made some of my own modifications.

It will pick up a file in the same path as the script called azure_ad_b2b.csv which needs to be comma delimited with just “InvitedUserEmailAddress,Name”

It will also prompt for the group name which you want to add invitees to, and bomb out if you get more or less than 1 result (because display names aren’t unique fields in Office 365)

Another prompt is for the project URL, which is where you want invitees to be sent to (which for me, is usually a SharePoint Online site). It’s also set to send the invites out from a generic service account, so change “specifyupn@yourdomain.com” in the send-mailmessage line to whatever you’re sending as. Feel free to ask any questions!

#1.) Install Azure AD PS module – https://www.powershellgallery.com/packages/AzureADPreview

#2.) provide O365 tenant admin cred

$cred = Get-Credential

Connect-AzureAD -Credential $cred

#2.second cred for O365 email account (merge var with above if for non-demo O365 tenant)

$adminemailcred = get-credential specifyupn@yourdomain.com

$groupname = Read-Host -Prompt 'Input the Group Name to add users to e.g. SharePoint Online XXX Portal External Full'

$project = Read-Host -Prompt 'Input the project name, 1 word e.g. TestSite'

#2.External User Security Group ID

$group = get-azureadgroup -SearchString $groupname | where {$_.dirsyncenabled -eq $null}

if ($group.count -ne 1) {echo "Not Exactly One Group Found"; break}

$projecturl = Read-host -Prompt 'Input the project URL XXX for https://yourdomain.sharepoint.com/XXX'

#3 import CSV, update url and csv location below.

$invitations = import-csv azure_ad_b2b.csv

foreach ($email in $invitations) {

$result= New-AzureADMSInvitation -InvitedUserEmailAddress $email.InvitedUserEmailAddress -InvitedUserDisplayName $email.Name -InviteRedirectUrl $projecturl -InvitedUserMessageInfo $messageInfo -SendInvitationMessage $false

$inviteurl = $result.InviteRedeemUrl

$userid = $result.InvitedUser.Id

#automatically add the new user to your Security Group

Add-AzureADGroupMember -objectid $group.objectid -RefObjectId $userid

#send the user a custom email from your Office 365 tenant. Supports HTML.

Send-MailMessage -To $result.InvitedUserEmailAddress -from specifyupn@yourdomain.com -Subject ‘Invitation to the $project ’ -Body “<h1>Congrats!</h1><br><strong>This is your invite</strong><br><br>Here:<br>$inviteurl <br>For <strong>help</strong>, contact admin@domain.com” -BodyAsHtml -smtpserver smtp.office365.com -usessl -Credential $adminemailcred -Port 587

}

 

How To Grep in PowerShell

For those who have lived in the Linux/Unix command line, the ‘grep‘ command is a commonly used way of finding something that you want in a chunk of data.

Øyvind Kallstad did a great writeup of comparing a bunch of ways to use PowerShell instead of grep which is worth reading.

The article covers a bunch of scenarios, and is centered around starting with the ‘grep’ command and working with it. However, there’s the other common use case of running a different command, then piping those results to grep to search for something.

This blogpost was triggered by Janet who asked me this fair question:

As with poor cute cats, there’s more than one way to skin PowerShell.

I had to do some research and asking around on this, because normally I’d filter out the property of the object I was looking at, and work with that. Using the get-process example:

get-process | where ProcessName -like "*foo*

That works, but it’s still a lot clunkier than what a grep user would expect. An easier way would be to use the ‘findstr‘ program (which also has a bunch of useful swtiches):

get-process | findstr foo

I say program because ‘findstr’ is not a PowerShell cmdlet, but it’s still native to Windows and works perfectly fine. It’s case sensitive though, so you need to use -i for case insensitive results.

That’s great for simple stuff, but we’re sort of breaking what PowerShell does. You’re no longer dealing with a standard PowerShell object, so further piping and processing won’t really work.

The ‘proper’ PowerShell way would be to use the ‘Where-Object’ command:

Get-Process | Where-Object {$_ | Select-String "foo"}

A bit longer, but you can shorten ‘Where-Object’ to ‘Where’. Although more involved, it’s good to get into the habit of doing it this way, so when you’re piping this to the next command, it still says as a standard object that can be read and manipulated.’

(Update 24th Feb 2017) As Steve_N points out in the comments section, there’s a much shorter way of doing this:

ps *foo*

That’s it. Many PowerShell commands have inbuilt aliases, including ‘get-process’. You can see what this is with the command ‘get-alias -definition get-process’

This shows that ‘gps’ and ‘ps’ are both aliases to the command  ‘get-process’. You can also create your own aliases with the ‘set-alias‘ command.

The ‘*foo*’ part works because the command assumes the -name switch has been used, which lets you define what criteria to search and show in the ProcessName field. This is the same way that many commands don’t need the -identity switch used, because it’s written to assume you’re going to tell it what identity/username/upn to work with.

This can also be piped to something else, so it’s a winner. It’s less ideal for scripts though, because it’s much harder to read, and you can’t assume that everyone will know the short alias of a full command.

Also note that this isn’t grep related at all, so part of the answer to the original question is that you may not even need grep or select-string as it adds unnecessary overhead of getting data and parsing it, whereas this updated example filters the data as it’s obtained.

(Update ends)

PowerShell isn’t a Linux/Unix command line, but Microsoft have incorporated many of the concepts from bash. If you still can’t bear to use PowerShell on Windows, there’s always the Linux Bash Shell on Windows.

Thanks again to Steve Mclnerey for the grep advice :)

PowerShell – ‘While’ Loop Statement

There’s a lot of different ways to loop commands in PowerShell, and here’s one I just learnt (thanks Nathan Kewley for spending the time talking me through this!):

Scenario: You create a brand new user in Active Directory, but need to wait for things to sync before you make a change to the user. If you want to automate these steps, you want to check that the user exists before running more commands against it.

Answer: The ‘While‘ statement. This lets you loop a command ‘while’ something is a certain value. For example, you may want a script to loop for two minutes, or until a certain value is true or false.

With my script below, it will check if the value $running is nothing (null), which it is because we just made it up. Because it’s true, it’ll then continue on to do whatever is in the curly brackets. Here, I’m running a command the enable a user in Skype for Business, but also setting the result of that as the variable $running.

If the command works, $running now has a value of the created user, so as it loops again to see if $running is null, it won’t be, and the ‘while’ statement is done.

If the command fails however, and shows the dangerous red warning around the user not existing, nothing gets set to the $running variable. That means, when it loops again, $running will still be null so it’ll try again and again and again.

while($running -eq $null){
 $running = Enable-CsUser -Identity testuser -SipAddress testuser@contoso.com
}

That’s rather dangerous of course, what if it’s forever $null? It’ll run forever, so we’d better put in some failsafes.

while($running -eq $null){
 if($CheckUser -le '10'){
  $CheckUser++
  start-sleep -s 10
  $running = Enable-CsUser -Identity testuser -SipAddress testuser@contoso.com
}
}

OK, this time we’re doing a couple more things. We’ve got two curly bracketed things to run now, the first is an ‘If’. If $CheckUser is less or equal to 10, then do the next curly bracket thing. The first time this runs that value again doesn’t exist because we just made it up, and nothing is less or equal to 10. The If statement is true, so it moves onto the next segment.

The $CheckUser++ command just adds ‘1’ to the value of $CheckUser – starting off at null or 0, so will turn into 1. As the statement loops, that number will increment all the way up to 11. Once it’s 11, the If statement is no longer true, so bombs out.

We’ve also added the start-sleep command, which is just a 10 second wait before doing anything. If we didn’t have that there, the 11 loops before it fails would be over incredibly quickly.

The last thing we can add is an event to occur once the ‘If’ statement is no longer true:

while($running -eq $null){
 if($CheckUser -le '10'){
  $CheckUser++
  start-sleep -s 10
  $running = Enable-CsUser -Identity testuser -SipAddress testuser@contoso.com
}else{
Throw "Unable to create SfB User"
}
}

All we’ve done here is added the ‘Else’ section, which only runs when the ‘If’ isn’t true. Once the $CheckUser variable hits 11, the ‘Else’ command runs and throws up an error, with the aptly named ‘Throw’ command.

Hopefully this is enough to explain the basics of the ‘While’ command.

Searching Multi-Valued Properties in PowerShell

I’ve been playing with Office 365 commands in PowerShell and had to do a search. Sounds simple, but depending on what you’re searching, some scenarios are less basic than others.

Everything in PowerShell is an object. Usually, a property in PowerShell has a single value, such as:

UserPrincipalName: afowler@contoso.com

which is one of the results from Get-MsolUser. However, another property is different:

AlternateEmailAddresses: {microsoft@contoso2.com}

Visually, the difference is just the {} braces that contain the value. These braces mean that the property has been built to contain multiple items, rather than a single item.

If I wanted to see a list of all UserPrincipalNames, I’d use this command:

Get-MsolUser -all | select UserPrincipalName

A nice list of UPNs would display on the screen. However, that same command against AlternateEmailAddress, all that comes up is a bunch of blank lines.

To make this work, we need to select the value and show all the expressions of each value:

get-msoluser -all | select @{Name=“AlternateEmailAddresses”;Expression={$_.AlternateEmailAddresses}}

To then search on those values with the ‘where’ command, you’d have to write it like this:

get-msoluser -all | select @{Name=“AlternateEmailAddresses”;Expression={$_.AlternateEmailAddresses}} | where {$_.AlternateEmailAddresses -like "*contoso*"}

The good news is, for a where search by itself, you can forget all that and go back to basics:

Get-MsolUser -all | Where AlternateEmailAddresses -like "*contoso*"

Because of this requirement on the Select command, it lead me down the wrong path for a bit. There’s other reading on how to list all the values of a multi-valued property

If you’re still lost and want to get started with PowerShell, try checking out this PowerShell Basics video