Wednesday, 4 October 2017

PowerShell : Check if user is member of local Adminstrators group

We can find whether the given user is member of local Administrators group or not by accessing ADSI WinNT Provider. In this post, I am going to write powershell script to check if an user is exists in local Administrators group in local machine and remote server.

Check if user is member of local Administrators group:

The following powershell commands checks whether the given user is member of Administrators group in local machine.
$user = "Morgan";
$group = "Administrators";
$groupObj =[ADSI]"WinNT://./$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 
$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})
If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}

Find if user is member of local Admins group in Remote server:

Use the below powershell command to check if user is member of Administrators group in remote computer.
$computer = "hp-pc" 
$user = "Morgan";
$group = "Administrators";
$groupObj =[ADSI]"WinNT://$computer/$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 
$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})
If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}

Check if multiple users are member of Administrators group:

Use the below powershell script to check if multiple users are member of local Admins group.
$users = "Morgan","TestUser1","TestUser2"
$group = "Administrators";
$groupObj =[ADSI]"WinNT://./$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 
$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})

ForEach ($user in $users) {
If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}}

Check if an user is member of a local group using PowerShell

We can easily find a local user is member of a local group by accessing ADSI WinNT Provider. In this post, I am going to share powershell script to check if local user is exists in a group, and check multiple users are member of a local group.

Check if local user is member of Administrators group:

The following powershell commands checks whether the given user is member of built-in Administrators group.
$user = "Morgan";
$group = "Administrators";
$groupObj =[ADSI]"WinNT://./$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 

$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})

If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}

Check if multiple users are member of a given local Group:

Run the below powershell command to check if multiple users are member of a given group.
$users = "Morgan","TestUser1","TestUser2"
$group = "Administrators";
$groupObj =[ADSI]"WinNT://./$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 

$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})

ForEach ($user in $users) {
If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}}

Check if users are member of a group in Remote Computer:

Use the below powershell command to check if users are member of a given group in remote machine/server.
$computer = "remote-pc" 
$users = "Morgan","TestUser1","TestUser2"
$group = "Administrators";
$groupObj =[ADSI]"WinNT://$computer/$group,group" 
$membersObj = @($groupObj.psbase.Invoke("Members")) 

$members = ($membersObj | foreach {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)})

ForEach ($user in $users) {
If ($members -contains $user) {
      Write-Host "$user exists in the group $group"
 } Else {
        Write-Host "$user not exists in the group $group"
}}

Tuesday, 26 September 2017

How to find Windows OS version using PowerShell

For troubleshooting purpose, or before deploy any software, it is good to know what is Windows operating system version that is currently running. We can easily find the OS details from My Computer properties, but if you want to get details from your customer machine to troubleshoot any issue, PowerShell is the best option to get all the required machine details.

In PowerShell, we can find operating system details in different ways, but to be safe we can use the WMI based cmdlet Get-WmiObject, this command is compatible from Windows PowerShell 2.0. Using this command we can query the WMI class Win32_OperatingSystem to get os version number:
(Get-WmiObject Win32_OperatingSystem).Version
The above command only returns the os version number. Run the following command to get the display name of your Windows version.
(Get-WmiObject Win32_OperatingSystem).Caption
Output :
Microsoft Windows 7 Ultimate
We can use select command to get the output of all the required OS related properties.
Get-WmiObject Win32_OperatingSystem |
Select PSComputerName, Caption, OSArchitecture, Version, BuildNumber | FL
We can use the Get-WmiObject cmdlet in short form gwmi.
(gwmi win32_operatingsystem).caption

Get OS version of a remote computer:

We can easily get the OS version details of a remote computer by adding the parameter -ComputerName to Get-WmiObject.
Get-WmiObject Win32_OperatingSystem -ComputerName "Remote_Machine_Name" |
Select PSComputerName, Caption, OSArchitecture, Version, BuildNumber | FL

Get OS details for a list of remote computers using PowerShell:

You can use the following powershell script to find OS version details for multiple remote computers. First create a text file named as computers.txt which includes one computer name in each line. You will get the output of machine name, OS name and version number in the csv file OS_Details.csv.
Get-Content C:\computers.txt  | ForEach-Object{
$os_name = (Get-WmiObject Win32_OperatingSystem -ComputerName $_ ).Caption
if(!$os_name){
$os_name = "The machine is unavailable"
$os_version = "The machine is unavailable"
}
else{
$os_version = (Get-WmiObject Win32_OperatingSystem -ComputerName $_ ).Version 
}
New-Object -TypeName PSObject -Property @{
ComputerName = $_
OSName = $os_name
OSVersion = $os_version 
}} | Select ComputerName,OSName,OSVersion |
Export-Csv C:\OS_Details.csv -NoTypeInformation -Encoding UTF8

Friday, 22 September 2017

Block and Unblock access to Office 365 users using PowerShell

Blocking access to an Office 365 account prevents anyone from using the account to sign in and access all the services and data in your Office 365 tenant. We can use the Azure AD powershell cmdlet Set-MsolUser to block user from login into Office 365 service (Ex: Mailbox, Planner, SharePoint, etc).

Block and Unblock an Office user account:

We need to set the user associated property BlockCredential to block user access to Office 365 service.
Set-MsolUser -UserPrincipalName username@domain.com -BlockCredential $true
The following command unblock the blocked user.
Set-MsolUser -UserPrincipalName username@domain.com -BlockCredential $false

Block multiple Office 365 user accounts:

We can use the command Get-MsolUser to fetch set of required Azure AD users with proper filter and then pipe the results to Set-MsolUser cmdlet to block access to every user.
Get-MsolUser -All | Where {$_.Department -eq "Testing"} |
Set-MsolUser -BlockCredential $true

Block bulk user accounts by import CSV file:

We may required to block access to bulk of user accounts, in this case we can have user ids in csv. We need to import csv file, and then pass every user to Set-MsolUser cmdlet. Consider the csv file Block_Users.csv that has users with the column header UserPrincipalName.
Import-Csv 'C:\Block_Users.csv' | ForEach-Object {
$upn = $_."UserPrincipalName"
Set-MsolUser -UserPrincipalName $upn -BlockCredential $true
}

Export blocked user accounts to CSV file:

Run the following command to export all the users that have been blocked to access Office 365 services.
Get-MsolUser -All | Where {$_.BlockCredential -eq $True} |
Select DisplayName,UserPrincipalName, BlockCredential |
Export-CSV "C:\\Blocked_Users.csv" -NoTypeInformation -Encoding UTF8

Hide and Un-hide users from GAL using Powershell

We can use the Exchange Powershell cmdlet Set-Mailbox to hide and un-hide mailbox users from Global Address List (GAL). We need to change the mailbox associated property HiddenFromAddressListsEnabled to hide user from GAL.

Before proceed, run the following command to load Exchange Online Powershell commands:
$365Logon = Get-Credential
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $365Logon -Authentication Basic -AllowRedirection
Import-PSSession $Session

Hide and Un-hide a mailbox user from Global Address Book:

Run the following command to hide a single mailbox user.
Set-Mailbox -Identity username@domain.com -HiddenFromAddressListsEnabled $true
The following command un-hide the given mailbox user from GAL.
Set-Mailbox -Identity username@domain.com -HiddenFromAddressListsEnabled $false

Hide multiple mailbox users from GAL:

We can use the Get-Mailbox cmdlet to fetch set of required mailboxes by applying proper filter and then pipe the results to Set-Mailbox command to hide every mailbox from GAL.
Get-Mailbox -ResultSize Unlimited | Where {$_.Office -eq "Office1"} |
Set-Mailbox -HiddenFromAddressListsEnabled $true

Import mailbox users from CSV and hide from GAL:

We may required to hide bulk mailboxes from Global Address Book, in this case we can store the mailbox user ids in csv file and import csv in powershell using Import-Csv cmdlet and pass every mailbox to Set-Mailbox cmdlet. Consider the CSV file Hide_Mailboxes.csv which contains mailbox users with the column header UserPrincipalName.
Import-Csv 'C:\Hide_Mailboxes.csv' | ForEach-Object {
$upn = $_."UserPrincipalName"
Set-Mailbox -Identity $upn -HiddenFromAddressListsEnabled $true
}

Export hidden mailboxes to CSV file:

We can use the powershell cmdlet Export-csv to export all the hidden mailbox users to csv.
Get-Mailbox -ResultSize Unlimited | Where {$_.HiddenFromAddressListsEnabled -eq $True} |
Select DisplayName,UserPrincipalName, HiddenFromAddressListsEnabled |
Export-CSV "C:\\Hidden_MailBoxes_GAL.csv" -NoTypeInformation -Encoding UTF8

Thursday, 21 September 2017

Find mailboxes hidden from the GAL using Powershell

We can easily get the list of all mailboxes that are currently hidden from Global Address Book using the Exchange Powershell cmdlet Get-Mailbox. The Get-Mailbox cmdlet includes the property HiddenFromAddressListsEnabled and this property indicates whether the mailbox is hidden from GAL or not. So we can query the mailboxes with where filter by checking whether the property HiddenFromAddressListsEnabled is set to true or not.
Get-Mailbox -ResultSize Unlimited | Where {$_.HiddenFromAddressListsEnabled -eq $True}
You can run the following command if you want the output consist of only selected properties :
Get-Mailbox -ResultSize Unlimited | Where {$_.HiddenFromAddressListsEnabled -eq $True} |
Select DisplayName,UserPrincipalName, HiddenFromAddressListsEnabled
We can also export all the hidden mailbox users to csv by simply using Export-csv cmdlet:
Get-Mailbox -ResultSize Unlimited | Where {$_.HiddenFromAddressListsEnabled -eq $True} |
Select DisplayName,UserPrincipalName, HiddenFromAddressListsEnabled |
Export-CSV "C:\\Hidden_MailBoxes_GAL.csv" -NoTypeInformation -Encoding UTF8

Tuesday, 19 September 2017

Set office 365 user's password to never expire using powershell

When you set password expiration policy for your Office 365 organization, it will apply to all Azure AD users. If you have requirements to set some individual user's password to never expire, you need to use Windows Powershell and you can achieve this by using the Azure AD Powershell cmdlet Set-MSOLUser.

Before proceed, connect to your online service by running the following command.
Import-Module MSOnline
$msolCred = Get-Credential
Connect-MsolService –Credential $msolCred

Set an individual user's password to never expire

Run the following command to set an user's password to never expire:
Set-MsolUser -UserPrincipalName <upn of user> -PasswordNeverExpires $true
For example, if the UserPrincipalName of the account is alexd@contoso.com, you can use below command:
Set-MsolUser -UserPrincipalName "alexd@contoso.com" -PasswordNeverExpires $true
You can find whether an user's password is set to never expire or not by running following command:
Get-MSOLUser -UserPrincipalName <upn of user> | Select DisplayName,PasswordNeverExpires

Set multiple users password to never expire

In some situations, we may required to update specific set of user's password to never expire. we can put the required user's upn in csv file and import the csv file and set passwordneverexpires setting. Consider the CSV file office365users.csv which contains users with the column header UserPrincipalName.
Import-Csv 'C:\office365users.csv' | ForEach-Object {
$upn = $_."UserPrincipalName"
Set-MsolUser -UserPrincipalName $upn -PasswordNeverExpires $true;
}

Thursday, 14 September 2017

Find SharePoint List items with unique permissions using powershell

We can easily find and retrieve SharePoint list items which has unique permissions using CSOM in Powershell. In this script, we are going to use GitHub open source library Load-CSOMProperties.ps1 to fetch extra properties (ex: HasUniqueRoleAssignments) in SharePoint CSOM API. You can refer this post : How to load additional CSOM properties in PowerShell for more details.

The following Powershell script get all files (or list items) which has unique (or explicit) permission entries from a given SharePoint Online document library. To use CSOM in Powershell, we need to load the required Microsoft SharePoint Online SDK assembly files.
#Add required references to SharePoint client assembly to use CSOM
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")

C:\Scripts\Load-CSOMProperties.ps1
  
$siteUrl="https://spotenant.sharepoint.com/sites/mysite1"
$UserName = "admin@spotenant.onmicrosoft.com"
$SecPwd = $(ConvertTo-SecureString 'myAdminPwd' -asplaintext -force) 
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,$SecPwd) 
$ctx.credentials = $credentials
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()
$list=$ctx.Web.Lists.GetByTitle("Documents")
$ctx.Load($list)
$ctx.ExecuteQuery()
$camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$camlQuery.ViewXml ="<View Scope='RecursiveAll' />";
$allItems=$list.GetItems($camlQuery)
$ctx.Load($allItems)
$ctx.ExecuteQuery()
 
foreach($item in $allItems)
{
Load-CSOMProperties -object $item -propertyNames @("HasUniqueRoleAssignments");
$ctx.ExecuteQuery();
if($item.HasUniqueRoleAssignments -eq $true)
{
Write-Host $item["FileRef"]
Write-Host "##############"
}
}

Friday, 8 September 2017

How to load additional CSOM properties in a PowerShell script

We can use CSOM (client-side object model) in different programming and scripting languages to manage SharePoint On-Premises and SharePoint Online objects, as you know loading additional properties in client side object is very challenging task. In .NET C#, we can easily load specific values using Lambda Expressions with SharePoint CSOM API, but we should have proper knowledge to write lambda expression in Windows PowerShell.

In this post, I am going to share how to use GitHub open source library Load-CSOMProperties.ps1 to fetch extra properties in SharePoint CSOM API. It includes the following steps.
  1. Copy the required Powershell script code from this GitHub location Load-CSOMProperties.ps1 or you also copy the same script code at the end of this post.
  2. Once you copied the script, open Notepad (or Text Document) and paste the copied script, then save the file with .ps1 extension (Ex: Load-CSOMProperties.ps1).
  3. Now you can load the saved Powershell script file in Powershell console by just entering the file path. Ex: C:\Scripts\Load-CSOMProperties.ps1
  4. Once you loaded the Load-CSOMProperties.ps1 file in Powershell console, you can call the function Load-CSOMProperties anywhere in your script to load extra properties for any client side object.

Example 1:

The below powershell script load the additional properties 'Url' and 'Title' of the object $web along with default properties.
C:\Scripts\Load-CSOMProperties.ps1

$web = $ctx.Web
Load-CSOMProperties -object $web -propertyNames @("AllProperties", "Url", "Title")
$ctx.ExecuteQuery()

Example 2:

The below powershell script load the property HasUniqueRoleAssignments in every list item and check if the item has any unique permission entry or not.
C:\Scripts\Load-CSOMProperties.ps1

foreach($listItem in $allItems)
{
Load-CSOMProperties -object $listItem -propertyNames @("HasUniqueRoleAssignments");
$ctx.ExecuteQuery();
if($listItem.HasUniqueRoleAssignments -eq $true)
{
Write-Host $listItem["FileRef"]
}
}

Source of Load-CSOMProperties.ps1:

<#
.Synopsis
    Facilitates the loading of specific properties of a Microsoft.SharePoint.Client.ClientObject object or Microsoft.SharePoint.Client.ClientObjectCollection object.
.DESCRIPTION
    Replicates what you would do with a lambda expression in C#. 
    For example, "ctx.Load(list, l => list.Title, l => list.Id)" becomes
    "Load-CSOMProperties -object $list -propertyNames @('Title', 'Id')".
.EXAMPLE
    Load-CSOMProperties -parentObject $web -collectionObject $web.Fields -propertyNames @("InternalName", "Id") -parentPropertyName "Fields" -executeQuery
    $web.Fields | select InternalName, Id
.EXAMPLE
   Load-CSOMProperties -object $web -propertyNames @("Title", "Url", "AllProperties") -executeQuery
   $web | select Title, Url, AllProperties
#>
function global:Load-CSOMProperties {
    [CmdletBinding(DefaultParameterSetName='ClientObject')]
    param (
        # The Microsoft.SharePoint.Client.ClientObject to populate.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObject")]
        [Microsoft.SharePoint.Client.ClientObject]
        $object,

        # The Microsoft.SharePoint.Client.ClientObject that contains the collection object.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 0, ParameterSetName = "ClientObjectCollection")]
        [Microsoft.SharePoint.Client.ClientObject]
        $parentObject,

        # The Microsoft.SharePoint.Client.ClientObjectCollection to populate.
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, Position = 1, ParameterSetName = "ClientObjectCollection")]
        [Microsoft.SharePoint.Client.ClientObjectCollection]
        $collectionObject,

        # The object properties to populate
        [Parameter(Mandatory = $true, Position = 1, ParameterSetName = "ClientObject")]
        [Parameter(Mandatory = $true, Position = 2, ParameterSetName = "ClientObjectCollection")]
        [string[]]
        $propertyNames,

        # The parent object's property name corresponding to the collection object to retrieve (this is required to build the correct lamda expression).
        [Parameter(Mandatory = $true, Position = 3, ParameterSetName = "ClientObjectCollection")]
        [string]
        $parentPropertyName,

        # If specified, execute the ClientContext.ExecuteQuery() method.
        [Parameter(Mandatory = $false, Position = 4)]
        [switch]
        $executeQuery
    )

    begin { }
    process {
        if ($PsCmdlet.ParameterSetName -eq "ClientObject") {
            $type = $object.GetType()
        } else {
            $type = $collectionObject.GetType() 
            if ($collectionObject -is [Microsoft.SharePoint.Client.ClientObjectCollection]) {
                $type = $collectionObject.GetType().BaseType.GenericTypeArguments[0]
            }
        }

        $exprType = [System.Linq.Expressions.Expression]
        $parameterExprType = [System.Linq.Expressions.ParameterExpression].MakeArrayType()
        $lambdaMethod = $exprType.GetMethods() | ? { $_.Name -eq "Lambda" -and $_.IsGenericMethod -and $_.GetParameters().Length -eq 2 -and $_.GetParameters()[1].ParameterType -eq $parameterExprType }
        $lambdaMethodGeneric = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($type.FullName),System.Object]])"
        $expressions = @()

        foreach ($propertyName in $propertyNames) {
            $param1 = [System.Linq.Expressions.Expression]::Parameter($type, "p")
            try {
                $name1 = [System.Linq.Expressions.Expression]::Property($param1, $propertyName)
            } catch {
                Write-Error "Instance property '$propertyName' is not defined for type $type"
                return
            }
            $body1 = [System.Linq.Expressions.Expression]::Convert($name1, [System.Object])
            $expression1 = $lambdaMethodGeneric.Invoke($null, [System.Object[]] @($body1, [System.Linq.Expressions.ParameterExpression[]] @($param1)))
 
            if ($collectionObject -ne $null) {
                $expression1 = [System.Linq.Expressions.Expression]::Quote($expression1)
            }
            $expressions += @($expression1)
        }


        if ($PsCmdlet.ParameterSetName -eq "ClientObject") {
            $object.Context.Load($object, $expressions)
            if ($executeQuery) { $object.Context.ExecuteQuery() }
        } else {
            $newArrayInitParam1 = Invoke-Expression "[System.Linq.Expressions.Expression``1[System.Func````2[$($type.FullName),System.Object]]]"
            $newArrayInit = [System.Linq.Expressions.Expression]::NewArrayInit($newArrayInitParam1, $expressions)

            $collectionParam = [System.Linq.Expressions.Expression]::Parameter($parentObject.GetType(), "cp")
            $collectionProperty = [System.Linq.Expressions.Expression]::Property($collectionParam, $parentPropertyName)

            $expressionArray = @($collectionProperty, $newArrayInit)
            $includeMethod = [Microsoft.SharePoint.Client.ClientObjectQueryableExtension].GetMethod("Include")
            $includeMethodGeneric = Invoke-Expression "`$includeMethod.MakeGenericMethod([$($type.FullName)])"

            $lambdaMethodGeneric2 = Invoke-Expression "`$lambdaMethod.MakeGenericMethod([System.Func``2[$($parentObject.GetType().FullName),System.Object]])"
            $callMethod = [System.Linq.Expressions.Expression]::Call($null, $includeMethodGeneric, $expressionArray)
            
            $expression2 = $lambdaMethodGeneric2.Invoke($null, @($callMethod, [System.Linq.Expressions.ParameterExpression[]] @($collectionParam)))

            $parentObject.Context.Load($parentObject, $expression2)
            if ($executeQuery) { $parentObject.Context.ExecuteQuery() }
        }
    }
    end { }
}

Thursday, 7 September 2017

Get Item Level Permissions in SharePoint using CSOM

In this post, I am going to write C# code sample to get item level permissions for all list items using CSOM in SharePoint On-Premises/SharePoint Online library. Every list items should have permission entries only if they have unique (or explicit) permissions assigned. If an item or document doesn't have any unique permission entry, then the item's permissions will be derived from its parent library permission.

Retrieve Item Level Permissions For List Items with CSOM

The below CSOM based C# code find all list items for a given SharePoint Online list (or library) and gets the permissions for every items if an item has unique permission.
public static void Get_Item_Level_Permissions_For_All_List_Items()
{
    string sitrUrl = "https://spotenant.sharepoint.com/sites/mysite";
    using (var ctx = new ClientContext(sitrUrl))
    {
        //ctx.Credentials = Your Credentials
        ctx.Load(ctx.Web, a => a.Lists);
        ctx.ExecuteQuery();
        List list = ctx.Web.Lists.GetByTitle("Documents");
        var listItems = list.GetItems(CamlQuery.CreateAllItemsQuery());
        //load all list items with default properties and HasUniqueRoleAssignments property
        ctx.Load(listItems, a => a.IncludeWithDefaultProperties(b => b.HasUniqueRoleAssignments));
        ctx.ExecuteQuery();
        foreach (var item in listItems)
        {
            Console.WriteLine("List item: " + item["FileRef"].ToString());
            if (item.HasUniqueRoleAssignments)
            {
                //load permissions if item has unique permission
                ctx.Load(item, a => a.RoleAssignments.Include(roleAsg => roleAsg.Member.LoginName,
                    roleAsg => roleAsg.RoleDefinitionBindings.Include(roleDef => roleDef.Name,
                    roleDef => roleDef.Description)));
                ctx.ExecuteQuery();
                foreach (var roleAsg in item.RoleAssignments)
                {
                    Console.WriteLine("User/Group: " + roleAsg.Member.LoginName);
                    List<string> roles = new List<string>();
                    foreach (var role in roleAsg.RoleDefinitionBindings)
                    {
                        roles.Add(role.Description);
                    }
                    Console.WriteLine("Permissions: " + string.Join(",", roles.ToArray()));
                    Console.WriteLine("----------------");
                }
            }
            else
            {
                Console.WriteLine("No unique permission found");
            }
            Console.WriteLine("###############");
        }
    }
}
The above code first fetch the list items and then load the role assignments for every items, so it includes multiple server requests, alternatively we can also load the list items and its permissions in single server request call.
List list = ctx.Web.Lists.GetByTitle("Documents");
var listItems = list.GetItems(CamlQuery.CreateAllItemsQuery());

//load all list items with default properties and HasUniqueRoleAssignments property and also
//load permissions of every items 
ctx.Load(listItems, a => a.IncludeWithDefaultProperties(b => b.HasUniqueRoleAssignments),
    permsn => permsn.Include(a => a.RoleAssignments.Include(roleAsg => roleAsg.Member.LoginName,
            roleAsg => roleAsg.RoleDefinitionBindings.Include(roleDef => roleDef.Name,
            roleDef => roleDef.Description))));
ctx.ExecuteQuery();
foreach (var item in listItems)
{
    Console.WriteLine("List item: " + item["FileRef"].ToString());
    if (item.HasUniqueRoleAssignments)
    {
        foreach (var roleAsg in item.RoleAssignments)
        {
            Console.WriteLine("User/Group: " + roleAsg.Member.LoginName);
            List<string> roles = new List<string>();
            foreach (var role in roleAsg.RoleDefinitionBindings)
            {
                roles.Add(role.Description);
            }
            Console.WriteLine("Permissions: " + string.Join(",", roles.ToArray()));
            Console.WriteLine("----------------");
        }
    }
    else
    {
        Console.WriteLine("No unique permission found");
    }
    Console.WriteLine("###############");
}

Wednesday, 30 August 2017

Get all files from a SharePoint Document Library using CSOM

In this post, I am going to write CSOM based C# code to retrieve all files in a SharePoint library using Client-side Object Model. Using this CSOM code you can easily fetch all the files from document library even it has files more than List View Threshold limit.

The List View Threshold defines the maximum limit to retrieve a number of documents in a single request. By default this limit is set to 5000 rows, and in OneDrive for Business (ODFB) this limit is 20000. So any library with more than 5000 files will return an error (Ex: The number of items in this list exceeds the list view threshold, which is 5000 items).

By using CSOM, you can retrieve documents page by page by setting row limit, this will avoid the list view threshold error as we are querying only certain amount of rows in a single request.

Retrieve all documents from a SharePoint library which has more than 5000 items

The following C# code fetch all files from a SharePoint online library. It will get 100 rows in every page. You can change the row limit as per your wish. In CAML query, we are setting the view as <View Scope='RecursiveAll'>, thus gets the documents from root folder and its sub folders by recursively.
public static List<ListItem> GetAllDocumentsInaLibrary()
{
    List<ListItem> items = new List<ListItem>();
    string sitrUrl = "https://spotenant.sharepoint.com/sites/yoursite";
    using (var ctx = new ClientContext(sitrUrl))
    {
        //ctx.Credentials = Your Credentials
        ctx.Load(ctx.Web, a => a.Lists);
        ctx.ExecuteQuery();

        List list = ctx.Web.Lists.GetByTitle("Documents");
        ListItemCollectionPosition position = null;
        // Page Size: 100
        int rowLimit = 100;
        var camlQuery = new CamlQuery();
        camlQuery.ViewXml = @"<View Scope='RecursiveAll'>
            <Query>
                <OrderBy Override='TRUE'><FieldRef Name='ID'/></OrderBy>
            </Query>
            <ViewFields>
                <FieldRef Name='Title'/><FieldRef Name='Modified' /><FieldRef Name='Editor' />
            </ViewFields>
            <RowLimit Paged='TRUE'>" + rowLimit + "</RowLimit></View>";
        do
        {
            ListItemCollection listItems = null;
            camlQuery.ListItemCollectionPosition = position;
            listItems = list.GetItems(camlQuery);
            ctx.Load(listItems);
            ctx.ExecuteQuery();
            position = listItems.ListItemCollectionPosition;
            items.AddRange(listItems.ToList());
        }
        while (position != null);
    }           
    return items;
}

Tuesday, 29 August 2017

Get all Items from a SharePoint List using CSOM

In this article, I am going to write C# code to find all items in a SharePoint list with Client-side Object Model (CSOM). This code also solves the List View Threshold problem. The List View Threshold defines the maximum limit to retrieve a number of items in a single query call. By default this limit is set to 5000, and in OneDrive for Business this limit is 20000. So any list with more than 5000 items will return an error (Ex: The number of items in this list exceeds the list view threshold, which is 5000 items).

By using CSOM, you can retrieve list items page by page by setting row limit, this will avoid the list view threshold error as we are querying only certain amount of rows in a single query.

Retrieve all items from a SharePoint library which has more than 5000 items

The following C# based CSOM code fetch all items from a SharePoint online library. It will get 50 rows in every page. You can change the row limit as per your wish.
public static List<ListItem> GetAllListItemsInaList1()
{
    List<ListItem> items = new List<ListItem>();
    string sitrUrl = "https://spotenant.sharepoint.com/sites/yoursite";
    using (var ctx = new ClientContext(sitrUrl))
    {
        //ctx.Credentials = Your Credentials
        ctx.Load(ctx.Web, a => a.Lists);
        ctx.ExecuteQuery();

        List list = ctx.Web.Lists.GetByTitle("Documents");
        ListItemCollectionPosition position = null;
        // Page Size: 50
        int rowLimit = 50;
        var camlQuery = new CamlQuery();
        camlQuery.ViewXml = @"<View Scope='RecursiveAll'>
            <Query>
                <OrderBy Override='TRUE'><FieldRef Name='ID'/></OrderBy>
            </Query>
            <ViewFields>
                <FieldRef Name='Title'/><FieldRef Name='Modified' /><FieldRef Name='Editor' />
            </ViewFields>
            <RowLimit Paged='TRUE'>" + rowLimit + "</RowLimit></View>";
        do
        {
            ListItemCollection listItems = null;
            camlQuery.ListItemCollectionPosition = position;
            listItems = list.GetItems(camlQuery);
            ctx.Load(listItems);
            ctx.ExecuteQuery();
            position = listItems.ListItemCollectionPosition;
            items.AddRange(listItems.ToList());
        }
        while (position != null);
    }           
    return items;
}

Monday, 28 August 2017

Get Office 365 Groups in which a User is a Member using Graph Api

Office 365 Groups is developed with collaboration in mind. It becomes a base service for most of existing Office 365 tools that we use already ( like writing documents, planner plans, calendar meetings, teams chat and exchange mail), so finding which unified groups a user is a member of is inevitable in many scenarios.

The Microsoft Graph Api endpoint https://graph.microsoft.com/v1.0/me/memberOf gets and return all the groups where a current user is memberOf. But it returns not only the unified groups, it also lists security groups and dynamic groups. So, you need to apply proper filter to get only Office 365 groups using memberOf endpoint.

Retrieve Office 365 Groups in which current user is a member of

The following API returns all the unified groups in which I am member of.
https://graph.microsoft.com/v1.0/me/memberOf/$/microsoft.graph.group?$filter=groupTypes/any(a:a eq 'unified')

Find Office 365 Groups where an user is a member of

You can also use the same memberOf endpoint to retrieve all the unified groups where a specific user is a member. You need to just replace the /me/ part in the above url with the user's id /users/<user-id>/
https://graph.microsoft.com/v1.0/users/<user-id>/memberOf/$/microsoft.graph.group?$filter=groupTypes/any(a:a eq 'unified')
Note: You can call the memberOf endpoint without Admin Consent after applying unified filter and adding extra parameter (/$/microsoft.graph.group?$filter=groupTypes/any(a:a eq 'unified')) in memberOf endpoint url. If you call without this filter, you will receive the following Access Denied error when you don't have proper admin consent.
"error": {
    "code": "Authorization_RequestDenied",
    "message": "Insufficient privileges to complete the operation.",
}

Tuesday, 22 August 2017

EWS - Create a meeting or appointment in other user calendar.

Using Exchange Web Services (EWS) managed api, we can access and add event to specific user's calendar either by delegate access or impersonation. Both methods are used in different scenario and they require different set of permissions.

A user or account that has been granted impersonation rights will have the same rights as the user whom they are impersonating. Typically, service accounts are given the ability to impersonate the mailbox owner. In that case, the impersonating account has full mailbox rights, just as the mailbox owner does.

With delegate access, the delegate can be granted more granular rights, up to and including full mailbox access. Delegate access can also be configured per folder, or per mailbox. For example, a user can grant the delegate read-only access to the Inbox, read-write access to a calendar folder, and so on. For more info : Impersonation vs Delegate Access

Before proceed, you have to download Microsoft Exchange Web Services Managed API 2.0 dll and add as reference in your C# project.

Create a meeting or appointment as a delegate in C# using EWS

You can use following C# code to create new event in any specific user's calendar. This sample assumes that the delegate user has been granted the appropriate permissions for the mailbox owner's Calendar folder.
ExchangeService exchService = new ExchangeService(ExchangeVersion.Exchange2013_SP1); 
exchService.UseDefaultCredentials = false; 
exchService.Credentials = new NetworkCredential("admin@o365domain.com", "pwd"); 
exchService.Url = new Uri("https://outlook.office365.com/Ews/Exchange.asmx"); 
exchService.PreAuthenticate = false; 
  
Appointment appointment = new Appointment(exchService); 
// Set the properties on the appointment object to create the appointment. 
appointment.Subject = "Sales Meeting"; 
appointment.Body = "Focus on pre-sale and marketing."; 
appointment.Start = DateTime.Now.AddDays(2); 
appointment.End = DateTime.Now.AddDays(2).AddHours(3); 
appointment.Location = "Room 111"; 
appointment.ReminderMinutesBeforeStart = 240; 

appointment.RequiredAttendees.Add("user3@o365domain.onmicrosoft.com"); 

// Save the meeting to the Calendar folder of the mailbox owner and send the meeting request.
// This method call results in a CreateItem call to EWS.
Folder calendar_Folder = Folder.Bind(exchService, new FolderId(WellKnownFolderName.Calendar, "user1@o365domain.com")); 
appointment.Save(calendar_Folder.Id, SendInvitationsMode.SendToNone); 

if (appointment.RequiredAttendees.Count > 0)
{
   // The appointment has attendees so send them the meeting request.
   appointment.Save(calendar_Folder.Id, SendInvitationsMode.SendToAllAndSaveCopy);
}
else
{
    // The appointment does not have attendees, so just save to calendar.
    appointment.Save(calendar_Folder.Id, SendInvitationsMode.SendToNone);
}
Please check this MSDN post : Add appointments by using Exchange impersonation to create appointment by using impersonation.

Impersonation vs Delegate Access in Exchange/Exchange Online

we can access and modify other (or specific) user's mail items, calendar events, and other exchange related objects using either by delegate access or impersonation with Exchange Web Services (EWS) Managed API. Both methods are used in different scenario and they require different set of permissions.

Impersonation is used in scenarios in which a single account needs to access many accounts (ex: service account). An application can be written to display mailbox data such as number of unread items, calendar, and so on. The application can use a dedicated service account to access multiple users’ mailboxes to display their respective data.

Delegate access is used in scenarios in which there needs to be a one-to-one relationship between users. One common application of delegate access is the sharing of calendars between users, such as when an admin manages an executive’s calendar, or a when handful of individuals working on a project need to coordinate calendars.

A user or account that has been granted impersonation rights will have the same rights as the user whom they are impersonating. Typically, service accounts are given the ability to impersonate the mailbox owner. In that case, the impersonating account has full mailbox rights, just as the mailbox owner does.

With delegate access, the delegate can be granted more granular rights, up to and including full mailbox access. Delegate access can also be configured per folder, or per mailbox. For example, a user can grant the delegate read-only access to the Inbox, read-write access to a calendar folder, and so on.

For more details, refer this article : Exchange Impersonation vs. Delegate Access

Monday, 21 August 2017

Create calendar event in Office 365 group using EWS

As Office 365 Group (Unified Group) is robustly becoming the base service for many of Office 365 features (i.e. Planner, MS Teams, etc..) managing planner team meetings and users appointment in group calendar is inevitable now. In this post, I am going to share .net based C# code to create meeting in Office 365 Group using Managed Exchange Web Services Api (EWS).

Before proceed, you have to download Microsoft Exchange Web Services Managed API 2.0 dll and add as reference in your C# project.
ExchangeService exchService = new ExchangeService(ExchangeVersion.Exchange2013_SP1); 
exchService.UseDefaultCredentials = false; 
exchService.Credentials = new NetworkCredential("admin@o365domain.com", "pwd"); 
exchService.Url = new Uri("https://outlook.office365.com/Ews/Exchange.asmx"); 
exchService.PreAuthenticate = false; 
  
Appointment appointment = new Appointment(exchService); 
// Set the properties on the appointment object to create the appointment. 
appointment.Subject = "Sales Meeting"; 
appointment.Body = "Focus on pre-sale and marketing."; 
appointment.Start = DateTime.Now.AddDays(2); 
appointment.End = DateTime.Now.AddDays(2).AddHours(3); 
appointment.Location = "Room 111"; 
appointment.ReminderMinutesBeforeStart = 240; 
appointment.RequiredAttendees.Add("user1@o365domain.onmicrosoft.co"); 
appointment.RequiredAttendees.Add("user2@o365domain.onmicrosoft.co"); 
// Bind the specified group calendar folder. 
Folder calendar_Folder = Folder.Bind(exchService, new FolderId(WellKnownFolderName.Calendar, "salesboard@o365domain.com")); 
appointment.Save(calendar_Folder.Id, SendInvitationsMode.SendToNone);
The parameter SendInvitationsMode specifies how meeting invitations to be send to its attendees. It includes following options:

  • SendToNone : Do not send meeting invitations.
  • SendOnlyToAll : Send meeting invitations to all attendees but do not save a copy of the meeting invitation in the organizer's Sent Items folder.
  • SendToAllAndSaveCopy " Send meeting invitations to all attendees and save a copy of the meeting invitation in the organizer's Sent Items folder.

Note: AFAIK, using Exchange Web Service (EWS) we can create only meeting not appointment in office 365 group.

Wednesday, 12 July 2017

Add or Remove member from a Group using Microsoft Graph .NET Client Library

Managing Group membership of Azure AD Group and Office 365 Group is one of a routine task for every Office 365 Admin. We can easily add or remove user from group using Microsoft Graph api. In this post, I am going to share C# .NET Client Library code to add and remove membership of an user.

Add member to a Group using Microsoft Graph .NET SDK:

private static void AddGroupMember(string groupId, User user) 
{ 
  var client = new GraphServiceClient(new DelegateAuthenticationProvider( 
      (requestMessage) => 
      { 
          requestMessage.Headers.Add("Authorization", "Bearer " + "<Access Token>"); 
          return Task.FromResult(0); 
  
      })); 

  client.Groups[groupId].Members.References.Request().AddAsync(user); 
}
For more info : Add member - Documentation - Microsoft Graph

Remove member from a Group using Microsoft Graph C# Client Library:

private static void RemoveGroupMember(string groupId, string memberId) 
{ 
  var client = new GraphServiceClient(new DelegateAuthenticationProvider( 
      (requestMessage) => 
      { 
          requestMessage.Headers.Add("Authorization", "Bearer " + "<Access Token>"); 
          return Task.FromResult(0); 
  
      })); 

 client.Groups[groupId].Members[memberId].Reference.Request().DeleteAsync(); 
}
For more info: Remove member - Documentation - Microsoft Graph

Thursday, 22 June 2017

List All Checked Out Files Using PowerShell

As a SharePoint administrator, if you are in need to find and get the list of all checked out files from a SharePoint document library, we can easily retrieve it by using client object model (CSOM) in Powershell.

The following Powershell script find and lists all the documents in a SharePoint Online document library which are checkout by current user and all other users.
#Add required references to SharePoint client assembly to use CSOM 
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")  
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles")
  
$siteUrl="https://spotenant.sharepoint.com/sites/testsite"
$UserName = "admin@spotenant.onmicrosoft.com"
$SecPwd = $(ConvertTo-SecureString 'adminPassword' -asplaintext -force) 
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,$SecPwd) 
$ctx.credentials = $credentials
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()
$list=$ctx.Web.Lists.GetByTitle("Documents")
$ctx.Load($list)
$ctx.ExecuteQuery()
$camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$camlQuery.ViewXml ="<View Scope='RecursiveAll' />";
$allItems=$list.GetItems($camlQuery)
$ctx.Load($allItems)
$ctx.ExecuteQuery()
 
foreach($item in $allItems)
{

$file = $ctx.Web.GetFileByServerRelativeUrl($item["FileRef"]);
$ctx.Load($file)
$ctx.Load($file.ListItemAllFields)
$CheckedOutByUser=$file.CheckedOutByUser     
$ModifiedBy=$file.ModifiedBy
$ctx.Load($CheckedOutByUser)
$ctx.Load($ModifiedBy)
try
{
$ctx.ExecuteQuery()
if($CheckedOutByUser.LoginName -ne $null){
Write-Host "##############"
Write-Host "File:" $file.Name
Write-Host "Url:" $item["FileRef"]
Write-Host "CheckedOutBy:" $CheckedOutByUser.LoginName
Write-Host "Last Modified By:" $ModifiedBy.LoginName
Write-Host "##############"
Write-Host ""
}}
catch{}
}
In the above powershell script first we are retrieving all the files from library and check every file if it is checked out by user or not, this process may time consuming in large document library, so alternatively we can use filter in caml query itself and retrieve checked out files alone.
#Add required references to SharePoint client assembly to use CSOM 
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")  
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles")
  
$siteUrl="https://spotenant.sharepoint.com/sites/testsite"
$UserName = "admin@spotenant.onmicrosoft.com"
$SecPwd = $(ConvertTo-SecureString 'adminPassword' -asplaintext -force)
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,$SecPwd) 
$ctx.credentials = $credentials
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()
$list=$ctx.Web.Lists.GetByTitle("Documents")
$ctx.Load($list)
$ctx.ExecuteQuery()
$camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$camlQuery.ViewXml ="<View Scope='RecursiveAll'><Query><Where><IsNotNull><FieldRef Name='CheckoutUser' /></IsNotNull></Where></Query></View>";
$allItems=$list.GetItems($camlQuery)
$ctx.Load($allItems)
$ctx.ExecuteQuery()
 
foreach($item in $allItems)
{
$file = $ctx.Web.GetFileByServerRelativeUrl($item["FileRef"]);
$ctx.Load($file)
$CheckedOutByUser=$file.CheckedOutByUser     
$ModifiedBy=$file.ModifiedBy
$ctx.Load($CheckedOutByUser)
$ctx.Load($ModifiedBy)
$ctx.ExecuteQuery()

Write-Host "##############"
Write-Host "File:" $file.Name
Write-Host "Url:" $item["FileRef"]
Write-Host "CheckedOutBy:" $CheckedOutByUser.LoginName
Write-Host "Last Modified By:" $ModifiedBy.LoginName
Write-Host "##############"
Write-Host ""
}

Wednesday, 21 June 2017

Get All List Items in Library using PowerShell with CSOM

In this article, I am going write a simple Powershell script using client object model (CSOM) to find and retrieve all files from a document library in SharePoint Online. To use csom in Powershell, we need to load the required Microsoft SharePoint Online SDK assembly files.

The below Powershell script simply load and list all the files from given document library in a SharePoint Online site. You need to replace sharepoint site url, list name and required admin credentials with your own details.
#Add required references to SharePoint client assembly to use CSOM 
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")  
[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles")
 
$siteUrl="https://spotenant.sharepoint.com/sites/testsite"
$UserName = "admin@spotenant.onmicrosoft.com"
$SecPwd = $(ConvertTo-SecureString 'adminPassword' -asplaintext -force) 
$ctx = New-Object Microsoft.SharePoint.Client.ClientContext($siteUrl) 
$credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,$SecPwd) 
$ctx.credentials = $credentials
$ctx.Load($ctx.Web)
$ctx.ExecuteQuery()
$list=$ctx.Web.Lists.GetByTitle("Documents")
$ctx.Load($list)
$ctx.ExecuteQuery()
$camlQuery = New-Object Microsoft.SharePoint.Client.CamlQuery
$camlQuery.ViewXml ="<View Scope='RecursiveAll' />";
$allItems=$list.GetItems($camlQuery)
$ctx.Load($allItems)
$ctx.ExecuteQuery()

foreach($item in $allItems)
{
Write-Host "##############"
Write-Host $item["FileRef"]
$file = $ctx.Web.GetFileByServerRelativeUrl($item["FileRef"]);
$ctx.Load($file)
$ctx.Load($file.ListItemAllFields)
$Author=$file.Author
$CheckedOutByUser=$file.CheckedOutByUser     
$ModifiedBy=$file.ModifiedBy
$ctx.Load($Author)
$ctx.Load($CheckedOutByUser)
$ctx.Load($ModifiedBy)
try
{
$ctx.ExecuteQuery()
Write-Host "File:" $file.Name 
Write-Host "Author:" $Author.LoginName
Write-Host "ModifiedBy:" $ModifiedBy.LoginName
if($CheckedOutByUser.LoginName -ne $null){
Write-Host "CheckedOutBy:" $CheckedOutByUser.LoginName
}}
catch{}
Write-Host "##############"
Write-Host ""
}
Along with file name and file relative url, the above script also retrieves useful information such as Author name of the file, modified username and if the file was checked out, it also returns the name of the user who currently checked-out the file and yet to checked-in.

Thursday, 15 June 2017

Hide Office 365 Group from GAL using Powershell

Hiding Office 365 Group from Global Address List (GAL) is one of the tedious job as there is no Admin UI to hide group mail from GAL. When you create an office 365 group it will not be hidden from GAL by default either it is public or private group. Currently Office 365 team accepted the user voice request to hide private groups from GAL by default. But for now, Powershell is the only option for Administrators to hide and show the groups from the GAL.

We can use the Exchange Online Powershell cmdlet Set-UnifiedGroup to hide group mail address from GAL. Before proceed, Connect Exchange Online Powershell module and use the following command.
Set-UnifiedGroup <group> -HiddenFromAddressListsEnabled $true
Actually we are setting the attribute HiddenFromAddressListsEnabled as true to hide group mail id from global address list. You can use the following command if you want set this property for all of your Office 365 Groups.
Get-UnifiedGroup | Set-UnifiedGroup -HiddenFromAddressListsEnabled $true
Normally you might want to hide only private groups. You can use below command to hide all the private groups from GAL:
Get-UnifiedGroup | Where-Object {$_.AccessType -eq 'Private'} | Set-UnifiedGroup -HiddenFromAddressListsEnabled $true
We can also list the groups that are disabled from address book using below powershell command:
Get-UnifiedGroup | Where-Object {$_.HiddenFromAddressListsEnabled -eq $true} | Select Alias,HiddenFromAddressListsEnabled
Hiding a Group from the GAL will only removes the availability of group in address list from external users, but it does not prevent other users to send email to the Group if they already know the address. If you want to restrict other users (other than group members) from sending message to the group, you need to set one more property - AcceptMessagesOnlyFromSendersOrMembers.
Set-UnifiedGroup <group> -AcceptMessagesOnlyFromSendersOrMembers <group>
If you want a group to accept messages from more than one group (multiple groups), you can give the group names as comma separated values in the above command.
Set-UnifiedGroup <group> -AcceptMessagesOnlyFromSendersOrMembers <group>,<group2>

Wednesday, 24 May 2017

How to allow external sender for Office 365 Groups using Powershell

Office 365 Group is a back end service for Microsoft Teams, Planner, and etc. By default Office 365 Groups are not configured to receive external messages either it is public or private group. But most of organizations using Teams, Planner and even standalone Office 365 Groups for external collaboration and conversation, so receiving mails from external domain users is inevitable.

We can use the Exchange Online Powershell cmdlet Set-UnifiedGroup to set the people outside the organization to send mail to a specific group. Before proceed, Connect Exchange Online Powershell module and use the following command to allow external sender.
Set-UnifiedGroup <group> -RequireSenderAuthenticationEnabled $false
Actually we need to set the attribute RequireSenderAuthenticationEnabled as false to remove the authentication check of external senders. You can use the below command if you want set this property for all the Office 365 Groups.
Get-UnifiedGroup | Set-UnifiedGroup -RequireSenderAuthenticationEnabled $false
You can use below command if you want allow guest users only for all public groups:
Get-UnifiedGroup | Where-Object {$_.AccessType -eq 'Public'} | Set-UnifiedGroup -RequireSenderAuthenticationEnabled $false
We can list all the groups with external sender access property using below powershell:
Get-UnifiedGroup | Select Alias,AccessType,RequireSenderAuthenticationEnabled
The below command lists only office 365 groups with guest sender access enabled.
Get-UnifiedGroup | Where-Object {$_.RequireSenderAuthenticationEnabled -eq $false} | Select Alias,RequireSenderAuthenticationEnabled
You can also enable via UI using Office 365 Admin center: Office 365 Portal -> Peoples -> Edit Group and set the option "Let people outside the organization email the group"

allow guest sender access for Office 365 Groups

Thursday, 23 March 2017

Enable or Disable In-Place Archive in Exchange Online using Powershell

In Exchange Online, users can have additional mailbox storage space by enabling In-Place Archive. Archive mailboxes also provide an alternate storage location in which to store historical messaging data. You can easily enable or disable In-Place Archive through Exchange Admin Center (EAC), but you should go with Powershell if you want to quickly enable the archive mailbox for all mailboxes in your organization.

Enable In-Place Archive for a mailbox

You can use Enable-Mailbox cmdlet to enable archiving for existing mailbox. You may already used Enable-Mailbox cmdlet to create mailbox for existing users who don't already have mailbox, you can use the same cmdlet to enable In-Place Archive by passing an extra parameter -Archive.

Before proceed, Connect Exchange Online Powershell module and use the following command to enable mailbox archiving.
Enable-Mailbox -Identity <mailbox user id> -Archive
The following command creates an In-Place archive for the existing user AlexD who already has a mailbox.
Enable-Mailbox -Identity AlexD -Archive

Disable In-Place Archive in a mailbox

As like enable archiving, you can use Disable-Mailbox cmdlet to disable archive feature in a mailbox. Use the below command to disable mailbox archive:
Disable-Mailbox -Identity <mailbox user id> -Archive
The below command removes archiving feature from the user AlexD's mailbox.
Disable-Mailbox -Identity AlexD -Archive

Wednesday, 15 February 2017

Read Multiple Users Profile Properties From SharePoint Online Using CSOM

This post is follow-up of the article http://www.morgantechspace.com/2016/09/read-sharepoint-user-profile-properties-csom.html, in previous post I have clearly explained about how to read current user profile properties, specific user (other user) properties and how to read only required profile properties using client object model (CSOM). One of our user asked the question "How to get a specific profile property (path to profile picture for example) for all of my Sharepoint's website users in one request", so I am writing this post to help every users.

Summary

Get All Profile Properties for Multiple SharePoint Online Users

In the below C# code, I have passed only list of SharePoint Online users, you can fetch all SharePoint Online users using your own best method and use it in below code. You can read users using Azure AD powershell cmdlet Get-MsolUser or you can fetch from your own csv file.
public static void GetMultipleUsersProfileProperties()
{
    string siteUrl = "https://spotenant-admin.sharepoint.com";

    var passWord = new SecureString();
    foreach (char c in "pass@word1".ToCharArray()) passWord.AppendChar(c);
    var credentials = new SharePointOnlineCredentials("admin@spotenant.onmicrosoft.com", passWord);
           
    // Connect to the sharepoint site client context.
    ClientContext clientContext = new ClientContext(siteUrl);
    clientContext.Credentials = credentials;

    // Get the PeopleManager object.
    PeopleManager peopleManager = new PeopleManager(clientContext);

    // Get multiple users
    List<string> Users = new List<string> { "admin@spotenant.onmicrosoft.com",
"alexw@spotenant.onmicrosoft.com", "benw@spotenant.onmicrosoft.com" };

    var results = new Dictionary<string, PersonProperties>();
    foreach (var user in Users)
    {
        string loginName = "i:0#.f|membership|" + user;  //claim format login name
        var personProperties = peopleManager.GetPropertiesFor(loginName);
        clientContext.Load(personProperties, p => p.AccountName, p => p.DisplayName,
                           p => p.UserProfileProperties);
        results.Add(loginName, personProperties);
    }
    clientContext.ExecuteQuery();

    foreach (var kvp in results)
    {
        if (kvp.Value.ServerObjectIsNull.HasValue && !kvp.Value.ServerObjectIsNull.Value)
        {
            Console.WriteLine(kvp.Value.DisplayName);
            Console.WriteLine("---------------------------------");
            foreach (var property in kvp.Value.UserProfileProperties)
            {
                Console.WriteLine(string.Format("{0}: {1}",
                    property.Key.ToString(), property.Value.ToString()));
            }                    
        }
        else
        {
            Console.WriteLine("User not found:"+kvp.Key);
        }
        Console.WriteLine("------------------------------");
        Console.WriteLine("          ");
    }
}

Get Specific Profile Properties for Multiple SharePoint Online Users

The below csom based C# code read only specific set of properties for set of SharePoint Online users.
public static void GetSpecificProfilePropertiesForAllUsers()
{
    string siteUrl = "https://spotenant-admin.sharepoint.com";

    var passWord = new SecureString();
    foreach (char c in "pass@word1".ToCharArray()) passWord.AppendChar(c);
    var credentials = new SharePointOnlineCredentials("admin@spotenant.onmicrosoft.com", passWord);

    // Connect to the sharepoint site client context.
    ClientContext clientContext = new ClientContext(siteUrl);
    clientContext.Credentials = credentials;

    // Get the PeopleManager object.
    PeopleManager peopleManager = new PeopleManager(clientContext);

    // Get multiple users - you can provide all users by fetching with different service
    // Ex: from Get-MsolUser powershell cmdlet
    List<string> Users = new List<string> { "admin@spotenant.onmicrosoft.com",
"alex2w@spotenant.onmicrosoft.com", "benw@spotenant.onmicrosoft.com" };

    var results = new Dictionary<string, IEnumerable<string>>();
    foreach (var user in Users)
    {
        string loginName = "i:0#.f|membership|" + user;  //claim format login name
        // Retrieve specific properties by using the GetUserProfilePropertiesFor method.  
        string[] profilePropertyNames = new string[] { "PersonalSpace", "PictureURL", "SPS-JobTitle" };
        UserProfilePropertiesForUser profilePropertiesForUser = new UserProfilePropertiesForUser(
            clientContext, loginName, profilePropertyNames);

        IEnumerable<string> profilePropertyValues = peopleManager.GetUserProfilePropertiesFor(profilePropertiesForUser);

        // Load the request for the set of properties. 
        clientContext.Load(profilePropertiesForUser);
        results.Add(loginName, profilePropertyValues);
    }
    clientContext.ExecuteQuery();

    foreach (var kvp in results)
    {
        if (kvp.Value != null && kvp.Value.Count() > 0)
        {
            Console.WriteLine("User :" + kvp.Key);
            // Returned collection contains only property values 
            foreach (var value in kvp.Value)
            {
                Console.WriteLine(value);
            }
        }
        else
        {
            Console.WriteLine("User not found:" + kvp.Key);
        }
    }
}

Tuesday, 14 February 2017

Disable AD User based on specific attribute using Powershell

In this article, I am going write powershell script to disable Active Directory user account by using user's specific property like employeeNumber, employeeID, etc...You can disable an ad user account by using the Active Directory powershell cmdlet Disable-ADAccount.
Disable-ADAccount -Identity <adaccount>
The Identity parameter specifies the Active Directory user that you want to disable. You can identify an account by its distinguished name (DN), GUID, security identifier (SID), or samAccountName.

Using the above command, you can not find user by using other AD attributes. So, we need to use another cmdlet Get-ADUser to find user using specific attribute and then we can pipe the result to Disable-ADAccount command to disable.

The following command search an AD user by user's EmployeeID using SQL like filter and disable the user.
Import-Module ActiveDirectory
Get-ADUser -Filter 'employeeID -like "1200547"' | Disable-ADAccount
You can also find an user by using well-known LDAP Filter. The following command find user by LDAP filter using user's EmployeeID and disable the user.
Import-Module ActiveDirectory
Get-ADUser -LDAPFilter '(employeeID=1200547)'  | Disable-ADAccount

Disable Bulk AD Users from CSV by User's EmployeeID

The following powershell script import AD users from csv file and disable by using user's EmployeeID property. Consider the CSV file Users.csv which contains set of AD users to disable with the attribute EmployeeID as one of the csv column header.
Import-Module ActiveDirectory
Import-Csv "C:\Users.csv" | ForEach-Object {
$employeeID = $_."EmployeeID"
Get-ADUser -LDAPFilter "(employeeID=$employeeID)"  | Disable-ADAccount
Write-Host "User $employeeID disabled"
}

Monday, 13 February 2017

Get the list of External users in SharePoint Online using Powershell

We can get the list of all external users in a SharePoint Online tenant using SharePoint Online Powershell cmdlet Get-SPOExternalUser and we can also find and list all the Office 365 guest users by using the Azure AD Powershell cmdlet Get-MsolUser. In this post, I am going to write script to export list of all the external user details to csv file.

Summary:


Get all the External users using Get-SPOExternalUser cmdlet

The below script list the external users from first page. You have to specify your SharePoint Online Admin Center url and Office 365 Admin Credentials to run the following commands.
#Connection to SharePoint Online
$SPOAdminSiteUrl="https://<YourDomain>-admin.sharepoint.com/" 
$365Logon = Get-Credential
Connect-SPOService -Url $SPOAdminSiteUrl -Credential $365Logon  

Get-SPOExternalUser -Position 0 -PageSize 50 | Select DisplayName,Email | FT
If you want to retrieve users from second page, you have to set the position as 1. The below command returns first 10 external users from the second page of the collection.
Get-SPOExternalUser -Position 1 -PageSize 10
You can also specify the parameter SiteUrl to retrieve external users only for a specific site.
Get-SPOExternalUser -Position 0 -PageSize 50 -SiteUrl <YourSiteUrl>

Fetch all the Office 365 External (Guest) users using Get-MsolUser cmdlet

The above command Get-SPOExternalUser will be very helpful if you have minimum number of external users. But it will be difficult if you have 100s of users as you have to fetch users page by page. So to overcome this problem, we can use the Azure AD Powershell cmdlet Get-MsolUser.
#Connection to Azure AD Module
Import-Module MSOnline
$365Logon = Get-Credential
Connect-MsolService –Credential $365Logon

Get-MsolUser -All | ? {$_.UserType -eq "Guest"} | Select DisplayName,SignInName | FT
The above command returns all the Office 365 external users (guest users). You can also apply more where filter to get users from specific domain. The below command returns users only from the domain TestDomain.com.
Get-MsolUser -All | ? {$_.UserType -eq "Guest"} | ? {$_.SignInName -like "*TestDomain.com"}

Export all the External user details to CSV file

You can easily export the external user details to csv file by using the cmdlet Export-Csv.
Get-MsolUser -All | ? {$_.UserType -eq "Guest"} | Select DisplayName,SignInName |
Export-CSV "C:\\External-Users.csv" -NoTypeInformation -Encoding UTF8

Thursday, 9 February 2017

Update Office 365 License features using Powershell

You can easily add a new license with required features and remove an existing license using Azure AD Powershell cmdlet Set-MsolUserLicense. In certain scenario you may need to update an existing license features (enable or disable license sub plans) using this cmdlet.

Use the below command to set a new license.
Set-MsolUserLicense -UserPrincipalName 'morgan@contoso.com' -AddLicenses 'contoso:ENTERPRISEPACK'
To assign multiple licenses, you have to provide AccountSkuId of all the licenses as comma (,) separated values.
Set-MsolUserLicense -UserPrincipalName 'morgan@contoso.com' -AddLicenses contoso:ENTERPRISEPACK,contoso:AAD_PREMIUM
You can enable only particular set of features while adding new license to an user. we have to use the powershell cmdlet New-MsolLicenseOptions to set license features that we want to disable (or remove) from new license.
$options = New-MsolLicenseOptions -AccountSkuId 'contoso:O365_BUSINESS_PREMIUM' -DisabledPlans OFFICE_BUSINESS,MCOSTANDARD
Set-MsolUserLicense -UserPrincipalName 'morgan@contoso.com' -LicenseOptions $options –AddLicenses 'contoso:O365_BUSINESS_PREMIUM'
Note: There is no option EnabledPlans like DisabledPlans, so we can't set only required features in straightforward way, we can achieve this only by excluding non-required features by using DisabledPlans option.

Update existing Office 365 License features

If you want to update or disable license features in existing license, you have to set only LicenseOptions in Set-MsolUserLicense cmdlet (exclude the parameter –AddLicenses).
$options = New-MsolLicenseOptions -AccountSkuId 'contoso:O365_BUSINESS_PREMIUM' -DisabledPlans OFFICE_BUSINESS,MCOSTANDARD
Set-MsolUserLicense -UserPrincipalName 'morgan@contoso.com' -LicenseOptions $options

Tuesday, 31 January 2017

Working with Array in Powershell

Array holds a list of data items. The Powershell array items can be the same type or different types.

Create or Initialize an Empty Array

The following syntax just creates an empty array object.
$myArray = @()

Create Array with Items

We can easily create predefined Array by just setting comma separated elements.
$myArray = "A","B","Hello","World"
Using explicit syntax:
$myArray = @(1,2,3,4,5)

Add values to an Array

We can add items to an array object by using the + operator.
$myArray = @(1,2,3,4,5)
$myArray = $myArray + 6
You can simplify the add operation by using assignment operator.
$myArray += 6
You can also add another array object using + operator.
$myArray += $secondArray

Read the contents of an array

We need to specify index number to retrieve an element from array, The Powershell array elements starting at 0th index.

Display all the elements in an array:
$myArray
This command returns the first element in an array:
$myArray[0]
This command displays the 2nd,5th,8th elements
$myArray[1,4,7]
Return the range of elements (4th element to the 8th element):
$myArray[3..7]
Return the last element in an array:
$myArray[-1]

Contains check in array

If you want to find an array contains a certain value, you don’t have to iterate through elements to compare the values with search term. Instead, you can apply filter with various comparison operators directly to the array.
$myArray = "A","B","C","Hello","World"
This command would check and return the array elements contains the string "Worl".
$myArray -like "*Worl*"
This command would check if the first three array elements contains the string "B" and return values.
$myArray[0..2] -like "*B*"

Set or Update array values

Use the assignment operator (=) to change or update values in an array.
$myArray[2]=23

Sort an elements

You can simply sort the elements by using Sort operator.
$myArray = $myArray | Sort

Delete an array and elements

You can easily delete an entire array object by setting its value as null
$myArray = $null
But Arrays are fixed-size, so, you can not easily remove values at different indexes. For this need, you need to use System.Array or System.Collections.Generic.List.

Monday, 30 January 2017

OneDrive for Business Storage Report using Powershell

You may need to see how much space your users are using in OneDrive for Business storage and need to ask them to free up space if they are getting close to your storage limit. We can retrieve the list of users with OneDrive feature provisioned by using SharePoint Online UserProfileService with Powershell.

Steps to Export OneDrive for Business Size Report:

  • Fetch all SharePoint Online users using UserProfileService.
  • Find if OneDrive for Business is provisioned or not.
  • Find Current Size and Storage Limit details.
  • Export Size and Storage Quota details to csv file.
Note: Replace the variable <your tenant name> with your Office 365 tenant name in all the occurrences and provide your own admin credentials.
$Result=@() 
# Specify your organization admin central url 
$AdminURI = "https://<your tenant name>-admin.sharepoint.com"

# Specify the User account for an Office 365 global admin in your organization
$AdminAccount = "admin@<your tenant name>.onmicrosoft.com"
$AdminPass = "admin_password"

$loadInfo1 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client")
$loadInfo2 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.Runtime")
$loadInfo3 = [System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint.Client.UserProfiles")

$sstr = ConvertTo-SecureString -string $AdminPass -AsPlainText -Force
$AdminPass = ""
$creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($AdminAccount, $sstr)
$UserCredential = New-Object System.Management.Automation.PSCredential -argumentlist $AdminAccount, $sstr

# Add the path of the User Profile Service to the SPO admin URL, then create a new webservice proxy to access it
$proxyaddr = "$AdminURI/_vti_bin/UserProfileService.asmx?wsdl"
$UserProfileService= New-WebServiceProxy -Uri $proxyaddr -UseDefaultCredential False
$UserProfileService.Credentials = $creds

# Set variables for authentication cookies
$strAuthCookie = $creds.GetAuthenticationCookie($AdminURI)
$uri = New-Object System.Uri($AdminURI)
$container = New-Object System.Net.CookieContainer
$container.SetCookies($uri, $strAuthCookie)
$UserProfileService.CookieContainer = $container

# Sets the first User profile, at index -1
$UserProfileResult = $UserProfileService.GetUserProfileByIndex(-1)
Write-Host "Starting- This could take a while."
$NumProfiles = $UserProfileService.GetUserProfileCount()
$i = 1

Connect-SPOService -Url $AdminURI -Credential $UserCredential

# As long as the next User profile is NOT the one we started with (at -1)...
While ($UserProfileResult.NextValue -ne -1) 
{
Write-Host "Checking profile $i of $NumProfiles"
# Look for the Personal Space object in the User Profile and retrieve it
# (PersonalSpace is the name of the path to a user's OneDrive for Business site. 
# Users who have not yet created a  OneDrive for Business site might not have this property)
$Prop = $UserProfileResult.UserProfile | Where-Object { $_.Name -eq "PersonalSpace" } 
$Url= $Prop.Values[0].Value
 
# If "PersonalSpace" exists, then OneDrive Profile provisioned for the user...
if ($Url) {
$siteUrl = "https://<your tenant name>-my.sharepoint.com"+ $Url.Substring(0,$Url.Length-1)

# Find size and storage limit
$temp = Get-SPOSite $siteurl -Detailed
if($temp)
{
$Result += New-Object PSObject -property @{ 
UserName = $temp.Title
UserPrincipalName = $temp.Owner
Size_inMB = $temp.StorageUsageCurrent
StorageQuota_inGB = $temp.StorageQuota/1024
WarningSize_inGB =  $temp.StorageQuotaWarningLevel/1024
}
}
}
# And now we check the next profile the same way...
$UserProfileResult = $UserProfileService.GetUserProfileByIndex($UserProfileResult.NextValue)
$i++
}
$Result | FT
You can also export the output into csv file:
$Result | Export-CSV "C:\\OneDrive-for-Business-Size-Report.csv" -NoTypeInformation -Encoding UTF8