Bulk Upload/Migrate File shares to SharePoint On-Premises and Online


There are many examples available that shows how to bulk migrate or upload files to SharePoint but none of those are providing a method to migrate with metadata.  The script below can be used to migrate complicated folder hierarchy to be uploaded to SharePoint Online or On-Premises.  The script is based on the the effort on Technet example https://gallery.technet.microsoft.com/PowerShell-Bulk-Upload-b9e9d600.  The same script can be modified to upload large amount of metadata.  You just need to export the files list to a CSV and then read values after the upload.  I will upload the other example later.

[CmdletBinding()]
param(
[Parameter(Mandatory=$false,Position=1)]
[String]$Credentials,
[Parameter(Mandatory=$True, Position=3)]
[String]$SiteURL,
[Parameter(Mandatory=$True, Position=4)]
[String]$DocLibName,
[Parameter(Mandatory=$True, Position=5)]
[String]$Folder,
[Parameter(Mandatory=$False, Position=6)]
[Switch]$Checkin,
[Parameter(Mandatory=$False, Position=7)]
[Switch]$O365
)

<#
    Add references to SharePoint client assemblies and authenticate to Office 365 site – required for CSOM

Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll”
Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll”
#>

Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll” -ErrorAction SilentlyContinue
Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll” -ErrorAction SilentlyContinue

Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Client”).location)
Add-Type -Path ([System.Reflection.Assembly]::LoadWithPartialName(“Microsoft.SharePoint.Client.runtime”).location)

function GetUserLookupString{
    [CmdletBinding()]
    param($context, $userString)
   
    try{
        $user = $context.Web.EnsureUser($userString)
        $context.Load($user)
        $context.ExecuteQuery()
       
        # The “proper” way would seem to be to set the user field to the user value object
        # but that does not work, so we use the formatted user lookup string instead
        #$userValue = New-Object Microsoft.SharePoint.Client.FieldUserValue
        #$userValue.LookupId = $user.Id
        $userLookupString = “{0};#{1}” -f $user.Id, $user.LoginName
    }
    catch{
        Write-Host “Unable to ensure user ‘$($userString)’.” -ErrorAction SilentlyContinue
        $userLookupString = $null
    }
   
    return $userLookupString
}

<#
    Define Functions
#>

<#
    Upload File – This function performs the actual file upload
#>
function UploadFile($DestinationFolder, $File)
{
    #Get the datastream of the file, assign it to a variable
    $FileStream = New-Object IO.FileStream($File.FullName,[System.IO.FileMode]::Open)

    #Create an instance of a FileCreationInformation object
    $FileCreationInfo = New-Object Microsoft.SharePoint.Client.FileCreationInformation

    #Indicate whether or not you would like to overwrite files in the event of a conflict
    $FileCreationInfo.Overwrite = $True

    #Make the datastream of the file you wish to create equal to the datastream of the source file
    $FileCreationInfo.ContentStream = $FileStream

    #Make the URL of the file equal to the $File variable which was passed to the function.  This will be equal to the source file name
    $FileCreationInfo.url = $File

    #Add the file to the destination folder which was passed to the function, using the FileCreationInformation supplied.  Assign this to a variable so that it can be loaded into context.
    $Upload = $DestinationFolder.Files.Add($FileCreationInfo)
    if($Checkin)
    {
        $Context.Load($Upload)
        $Context.ExecuteQuery()
        if($Upload.CheckOutType -ne “none”)
        {
            $Upload.CheckIn(“Checked in by Administrator”, [Microsoft.SharePoint.Client.CheckinType]::MajorCheckIn)
        }
    }
    $Context.Load($Upload)
    $Context.ExecuteQuery()

    Write-Host “Adding Metadata to File Item…$($File.FullName)”
            $listItem = $Upload.ListItemAllFields
            $Context.Load($listItem)
            #$Context.ExecuteQuery()
            $Context.ExecuteQuery()
   
            $Item = $List.GetItemById($listItem.Id);
            $Context.Load($Item)

            $Owner = $File.GetAccessControl().Owner
            $LastModifiedBy =Get-ChildItem $File.FullName | Sort {$_.LastWriteTime} | select -last 1 | foreach {$a=$_;$b=Get-Acl $_.FullName; Add-Member -InputObject $b -Name “LastWriteTime” -MemberType NoteProperty -Value $a.LastWriteTime;$b}
           
            if($Owner -eq “BUILTIN\Administrators”)
            {
                $Author = “hp\spadmin”
            }

            if($LastModifiedBy.Owner -eq “BUILTIN\Administrators”)
            {
                $Editor = “hp\spadmin”
            }
     
            $Author = GetUserLookupString $Context $Author
            if($Author)
            {
                $Item[“Author”] = $Author
            }
            $Editor = GetUserLookupString $Context $Editor
            if($Editor)
            {
                $Item[“Editor”] = $Editor
            }
       
            [System.DateTime]$CreatedOnDate = $File.CreationTime
            [System.DateTime]$ModifiedOnDate = $File.LastWriteTime

            $Item[“Created”] = $CreatedOnDate
            $Item[“Modified”] = $ModifiedOnDate
            try
            {
                $Item.Update()
                $Context.ExecuteQuery()
            }
            catch
            { Write-Host “Exception Occured in reading list item properties… But script will continue” }
           
}

<#
    Create Folder Function.
#>
function PopulateFolder($ListRootFolder, $FolderRelativePath, $FolderFullPath)
{
    #split the FolderRelativePath passed into chunks (between the backslashes) so that we can check if the folder structure exists
    $PathChunks = $FolderRelativePath.substring(1).split(“\”)

    #Make sure we start with a fresh WorkingFolder for every folder passed to the function
    if($WorkingFolder)
    {
        Remove-Variable WorkingFolder
    }

    #Start with the root folder of the list, load this into context
    $WorkingFolder = $ListRootFolder
    $Context.load($WorkingFolder)
    $Context.ExecuteQuery()

    #Load the folders of the current working folder into context
    $Context.load(($WorkingFolder.folders))
    $Context.executeQuery()

    #Set the FileSource folder equal to the absolute path of the folder that passed to the function
    $FileSource = $Folder + $FolderRelativePath
   
    #Loop through the folder chunks, ensuring that the correct folder hierarchy exists in the destination
    foreach($Chunk in $PathChunks)
    {
        #Check to find out if a subfolder exists in the current folder that matches the patch chunk being evaluated
        if($WorkingFolder.folders | ? {$_.name -eq $Chunk})
        {
            #Log the status to the PowerShell host window
            Write-Host “Folder $Chunk Exists in” $WorkingFolder.name -ForegroundColor Green

            #Since we will be evaluating other chunks in the path, set the working folder to the current folder and load this into context.
            $WorkingFolder = $WorkingFolder.folders | ? {$_.name -eq $Chunk}
            $Context.load($WorkingFolder)
            $Context.load($WorkingFolder.folders)
            $Context.ExecuteQuery()

        }
        else
        {
            #If the folder doesn’t exist, Log a message indicating that the folder doesn’t exist, and another message indicating that it is being created
            Write-Host “Folder $Chunk Does Not Exist in” $WorkingFolder.name -ForegroundColor Yellow
            Write-Host “Creating Folder $Chunk in” $WorkingFolder.name -ForegroundColor Green
           
            #Load the working folder into context and create a subfolder with a name equal to the chunk being evaluated, and load this into context
            $Context.load($WorkingFolder)
            $Context.load($WorkingFolder.folders)
            $Context.ExecuteQuery()

            $WorkingFolder= $WorkingFolder.folders.add($Chunk)
            $Context.load($WorkingFolder)
            $Context.load($WorkingFolder.folders)
            $Context.ExecuteQuery()

            #$FolderRelativePath = “C:\Demo\Corporate Docs”
            $LocalFolder = Get-Item -Path $FolderFullPath

            $Web = $Context.Web
            $Context.Load($Web)
            $Context.ExecuteQuery()

            $SPFolder = $Web.GetFolderByServerRelativeUrl($WorkingFolder.ServerRelativeUrl)
            $Context.Load($SPFolder)
            $Context.Load($SPFolder.ListItemAllFields)
            $Context.ExecuteQuery()
            $FolderItemId = $SPFolder.ListItemAllFields.Id
            $FItem = $List.GetItemById($FolderItemId)
            $Context.Load($FItem)
            $Context.ExecuteQuery()

            #$Query = New-Object Microsoft.SharePOint.Client.CamlQuery
            #$Query.ViewXml = “<View Scope=’RecursiveAll’><Query><Where><And><Eq><FieldRef Name=’ContentType’/><Value Type=’Text’>Folder</Value></Eq><Eq><FieldRef Name=’FileLeafRef’/><Value Type=’Text’>” + $Chunk + “</Value></Eq></And></Where></Query></View>”

            $FItem[“Created”] = $LocalFolder.CreationTime.ToString()
            $FItem[“Modified”] = $LocalFolder.LastWriteTime.ToString()
            $CreatedBy = $LocalFolder.GetAccessControl().Owner
            if($CreatedBy -eq “BUILTIN\Administrators”)
            {
                $CreatedBy = “hp\spadmin”
            }

            $Author = GetUserLookupString $Context $CreatedBy
            if($Author)
            {
                $FItem[“Editor”] = $Author
                $FItem[“Author”] = $Author
            }
            $Context.ExecuteQuery()

            try
            {
                $FItem.Update()
                $Context.ExecuteQuery()
            }
            catch
            { Write-Host “Exception Occured in updating folder properties… But script will continue” }

        }

    }

    #Folder is confirmed existing or created – now it’s time to list all files in the source folder, and assign this to a variable
    $FilesInFolder = Get-ChildItem -Path $FileSource | ? {$_.psIsContainer -eq $False}
   
    #For each file in the source folder being evaluated, call the UploadFile function to upload the file to the appropriate location
    Foreach ($File in ($FilesInFolder))
    {

        #Notify the operator that the file is being uploaed to a specific location
        Write-Host “Uploading file ” $file.Name “to” $WorkingFolder.name -ForegroundColor Cyan

        #Upload the file
        UploadFile $WorkingFolder $File

    }
   
   
   
}

<#
    Bind your context to the site collection
#>
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL)

<#
    Create a credential object using the username and password supplied
#>

if($O365)
{
    $Creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credentials.UserName,$Credentials.Password)
}
else
{
    $Creds = [System.Net.CredentialCache]::DefaultCredentials
    #$Creds = New-Object System.Net.NetworkCredential($Credentials.UserName,$Credentials.Password)
}
#if($O365)
#{
#    $Creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,(ConvertTo-SecureString $Password -AsPlainText -Force))
#}
#else
#{
#    #$Creds = New-Object System.Net.NetworkCredential($UserName, (ConvertTo-SecureString $Password -AsPlainText -Force))
#}

<#
    Set the credentials that are used in the context.
#>
$Context.Credentials = $Creds

<#
    Retrieve the library, and load it into the context
#>
$List = $Context.Web.Lists.GetByTitle($DocLibName)
$Context.Load($List)
$Context.ExecuteQuery()

$List.Fields.GetByInternalNameOrTitle(“Author”).ReadOnlyField = $true
$List.Fields.GetByInternalNameOrTitle(“Editor”).ReadOnlyField = $true
$List.Fields.GetByInternalNameOrTitle(“Created”).ReadOnlyField = $true
$List.Fields.GetByInternalNameOrTitle(“Modified”).ReadOnlyField = $true
#$List.EnableVersioning = $True
$List.Update()
$Context.ExecuteQuery()

#Get a recursive list of all folders beneath the folder supplied by the operator
$AllFolders = Get-ChildItem -Recurse -Path $Folder |? {$_.psIsContainer -eq $True}

#Get a list of all files that exist directly at the root of the folder supplied by the operator
$FilesInRoot = Get-ChildItem -Path $Folder | ? {$_.psIsContainer -eq $False}

#Upload all files in the root of the folder supplied by the operator
Foreach ($File in ($FilesInRoot))
{

    #Notify the operator that the file is being uploaded to a specific location
    Write-Host “Uploading file ” $File.Name “to” $DocLibName -ForegroundColor Cyan

    #Upload the file
    UploadFile($list.RootFolder) $File
   

}

#Loop through all folders (recursive) that exist within the folder supplied by the operator
foreach($CurrentFolder in $AllFolders)
{
    #Set the FolderRelativePath by removing the path of the folder supplied by the operator from the fullname of the folder
    $FolderRelativePath = ($CurrentFolder.FullName).Substring($Folder.Length)
   
    #Call the PopulateFolder function for the current folder, which will ensure that the folder exists and upload all files in the folder to the appropriate location
    PopulateFolder ($list.RootFolder) $FolderRelativePath $CurrentFolder.FullName
}

#Calling the script for SharePoint on-premises

$Creds = Get-Credential
.\BulkUploadSharePointCSOM.ps1 -SiteURL “https://portal.hp.com” -DocLibName “Metadata” -Folder “C:\Demo”

#Calling the script for SharePoint online

$Creds = Get-Credential
.\BulkUploadSharePointCSOM.ps1 -SiteURL “https://portal.hp.com” -DocLibName “Metadata” -Folder “C:\Demo” -Credentials $Creds -Checkin -O365

Creating Microsoft Team using Graph


In the previous script I provide a way to browse and export Microsoft Teams Information using Graph but this time I have taken that to next level.  The script below will allow you to create a Team, Channel, Members, Owners, Tabs and Message, all using Microsoft Graph.  The script is created for someone who do not have great knowledge of Graph and may act as starting point.  It is not a complete solution but can be use to provision team with basic to medium complexities.  Creating Team from Graph and PowerShell may look a bit complex but once you read the graph documentation, It become very easy.  The script uses both v1.0 and beta endpoints based on their availability. 

The CSV format is as following

ID,TeamName,MailNickName,TeamType,Classification,DeleteExistingTeam,Description,Channels,Owners,Members,Tabs,
1,TeamMXC,TeamMXC,Private,Internal,Yes,Demo Team will provide support,Channel1#Channel2#Channel3,GarthF@tenant.onmicrosoft.com#jerryyasir@tenant.onmicrosoft.com,GarretV@tenant.onmicrosoft.com#abrown@tenant.onmicrosoft.com,C:\temp\CreateTeam-Members.csv,Channel1;Tab1;Tab2#Channel2;Tab3;Tab4

Script

#$scopes = @(“Group.Read.All”,”Group.ReadWrite.All”,”User.ReadWrite.All”, “Directory.Read.All”,”Reports.Read.All”)
$scopes = $null

$ApplicationID = “”
$Password = ”
$appaaddomain = ‘tenant.onmicrosoft.com’
$CurrentUser = “jerryyasir@tenant.onmicrosoft.com”
 
$GraphURL = “https://graph.microsoft.com/beta”
$graphV1Endpoint = “https://graph.microsoft.com/v1.0″

#Establish connection
If($scopes.Length -gt 0){
    Connect-PnPOnline -Scopes $scopes
} elseif($ApplicationID.Length -gt 0) {
    #Connect-PnPOnline -AppId $ApplicationID -AppSecret $Password -AADDomain $appaaddomain
    Connect-PnPMicrosoftGraph -AppId $ApplicationID -AppSecret $Password -AADDomain $appaaddomain
} else {
    write-host ‘Connection issue’ -ForegroundColor Red
    exit
}

$token = Get-PnPAccessToken

$CSVPath = “C:\temp\CreateTeam-Basic-Graph.csv”

$Data = Import-Csv -Path $CSVPath

foreach($team in $Data)
{
    Write-Host “Team Creation Started…” $team.TeamsName -ForegroundColor Yellow
    $TeamName       = $team.TeamName
    $displayname    = $team.TeamName
    $MailNickName    = $team.MailNickName
    $AccessType     = $team.TeamType
    $Description    = $team.Description
    $Classification = $team.Classification
    $TeamChannels   = $team.Channels
    $TeamMembers    = $team.Members
    $TeamOwners     = $team.Owners
    $DeleteExistingTeam    = $team.DeleteExistingTeam
 
    #$url = “$GraphURL/groups?`$filter=displayName eq ‘TeamABC'”
    $getTeamFromGraphUrl = “$GraphURL/groups?`$filter=displayName eq ‘” + $TeamName + “‘”
    $teamAlreadyExistResponse = Invoke-RestMethod -Uri $getTeamFromGraphUrl -Headers @{Authorization = “Bearer $token”}
    if($teamAlreadyExistResponse.value.Count -gt 0){
        foreach($r in $teamAlreadyExistResponse.value){
            if($r.resourceProvisioningOptions -eq ‘Team’){
       
                $GroupCreatedDateTime = $r.createdDateTime
               
                $TeamID = $r.id
                $TeamsOwnerUrl =  “$GraphURL/groups/$TeamID/owners”
                $teamsOwnersResponse = Invoke-RestMethod -Uri $TeamsOwnerUrl -Headers @{Authorization = “Bearer $token”}
                $OwnerName = “”
                if($teamsOwnersResponse)
                {
                    Write-Host ”    Team Owners:”
                    foreach($owner in $teamsOwnersResponse.value)
                    {
                        $OwnerName += $owner.displayName + “;”
                    }
                    $teamsOwnersResponse = $null
                }
                write-host “The Team $($r.displayname) Created On $GroupCreatedDateTime Owned by $OwnerName already exist on the tenant.” -ForegroundColor Yellow
               
                if($DeleteExistingTeam)
                {
                   
                }
           
            }
            else {
                write-host $r.displayname “is an O365 Group.” -ForegroundColor Green
            }
        }
    }
   
    else
    {
        $arrayMembers = New-Object System.Collections.ArrayList
        try
        {
            $arrTeamMembers = $TeamMembers -split “#”
            if($arrTeamMembers)
            {
                for($i =0; $i -le ($arrTeamMembers.count – 1) ; $i++)
                {
                    $MemberUrl = “$graphV1Endpoint/users”
                    $UserUserPrincipalName = $arrTeamMembers[$i]
                    if($UserUserPrincipalName -ne $CurrentUser)
                    {
                        #$arrayMembers.Add(“$MemberUrl/$UserUserPrincipalName”)
                        $arrayMembers.Add($UserUserPrincipalName)
                    }
                }
            }
        }
        Catch
        {
            Write-Host “There is issue with Channel settings in CSV, Check and Fix:. $teamchannels”
        }
               
        $arrayOwners = New-Object System.Collections.ArrayList
               
        try
        {
            $arrTeamOwners = $TeamOwners -split “#”
            if($arrTeamOwners)
            {
                for($i =0; $i -le ($arrTeamOwners.count – 1) ; $i++)
                {
                    $OwnerUserPrincipalName = $arrTeamOwners[$i]
                    $OwnerUrl = “$graphV1Endpoint/users”
                    $arrayOwners.Add($OwnerUserPrincipalName)
                }
            }
        }
        Catch
        {
            Write-Host “There is issue with Channel settings in CSV, Check and Fix:. $teamchannels”
        }

        $arryGroupType = @(“Unified”)
       
        $arrayOwnersInREST = New-Object System.Collections.ArrayList
        $arrayMembersInREST = New-Object System.Collections.ArrayList
        foreach($Member in $arrayMembers)
        {
            $FindMemberUrl = “https://graph.microsoft.com/v1.0/users/” + $Member + “?`$Select=Id”
            $Response = Invoke-RestMethod -Uri $FindMemberUrl -Headers @{Authorization = “Bearer $token”} -Method Get -ContentType “application/json” -Verbose
            if($Response)
            {
                $Response.id
                $MembersUrl = “https://graph.microsoft.com/v1.0/Users/$($Response.id)”
                $arrayMembersInREST.Add($MembersUrl)
            }
        }
        foreach($owner in $arrayOwners)
        {
            $FindOwnerUrl = “https://graph.microsoft.com/v1.0/users/” + $owner + “?`$Select=Id”
            $Response = Invoke-RestMethod -Uri $FindOwnerUrl -Headers @{Authorization = “Bearer $token”} -Method Get -ContentType “application/json” -Verbose
            if($Response)
            {$Response.id
                $OwnerUrl = “https://graph.microsoft.com/v1.0/Users/$($Response.id)”
                $arrayOwnersInREST.Add($OwnerUrl)
            }
        }
       
        $FindOwnerUrl = “https://graph.microsoft.com/v1.0/users/” + $CurrentUser + “?`$Select=Id”
        $Response = Invoke-RestMethod -Uri $FindOwnerUrl -Headers @{Authorization = “Bearer $token”} -Method Get -ContentType “application/json” -Verbose
        if($Response)
        {
            $Response.id
            $CurrentUserAsMemberUrl = “https://graph.microsoft.com/v1.0/directoryobjects/$($Response.id)”
                   
            $CurrentUserAsMember = “$graphV1Endpoint/groups/$TeamID/members/`$ref”
            $body = [ordered]@{
                “@odata.id” = $CurrentUserAsMemberUrl
            }
            $bodyJSON = $body | ConvertTo-Json 
            Invoke-RestMethod -Uri $CurrentUserAsMember -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
                   
            $PlannerUri = “$GraphURL/planner/plans”
            $body = [ordered]@{
                owner = $TeamID;
                title = $TeamName;
            }
            $bodyJSON = $body | ConvertTo-Json 
            Invoke-RestMethod -Uri $PlannerUri -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
            Write-Host ”      A planner plan $TeamName is now added to the Team.” -ForegroundColor Green
        }
               
        $GroupUrl = “$graphV1Endpoint/groups”
        $body = [ordered]@{
            displayName = $TeamName;
            description = $Description;
            groupTypes = $arryGroupType;
            mailEnabled= $true;
            mailnickname = $MailNickName;
            securityEnabled=$false;
            “members@odata.bind” = $arrayMembersInREST;
            “owners@odata.bind” = $arrayOwnersInREST;
        }
       
        $bodyJSON = $body | ConvertTo-Json 
        $Response = Invoke-RestMethod -Uri $GroupUrl -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
       
        $GroupCreationResponse = $null
        $Stoploop = $false
        $GroupId = $null
        do {
            $GroupQueryUrl=$GroupUrl + “/?`$filter=displayName eq ‘” + $TeamName + “‘”
            $GroupCreationResponse = Invoke-RestMethod -Uri $GroupQueryUrl -Headers @{Authorization = “Bearer $token”} -Method Get -Verbose 
            if($GroupCreationResponse.value.Count -gt 0){
                $Stoploop = $true
                foreach($val in $GroupCreationResponse.value)
                {
                    $GroupId = $val.Id
                }
               
            }
        }
        While ($Stoploop -eq $false)
       
        $memberSettings = @{}
        $memberSettings.Add(“allowCreateUpdateChannels”,$true)
       
        $messagingSettings = @{}
        $messagingSettings.Add(“allowUserEditMessages”,$true)
        $messagingSettings.Add(“allowUserDeleteMessages”,$true)

        $funSettings = @{}
        $funSettings.Add(“allowGiphy”,$true)
        $funSettings.Add(“giphyContentRating”,$true)
       
        $TeamCreationUrl = “$GraphURL/groups/$GroupId/team”
        $body = [ordered]@{
        }
        $bodyJSON = $body | ConvertTo-Json
       
        $TeamCreationResponse = $null
        $TeamCreationResponse=Invoke-RestMethod -Uri $TeamCreationUrl -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Put -ContentType “application/json” -Verbose
        if($TeamCreationResponse -eq $null)
        {
            $Stoploop = $false
            do {
                $TeamCreationResponse = Invoke-RestMethod -Uri $getTeamFromGraphUrl -Headers @{Authorization = “Bearer $token”} -Method Get -Verbose 
                if($TeamCreationResponse){
                    $Stoploop = $true
                }
            }
            While ($Stoploop -eq $false)
        }
       
     
        if($TeamCreationResponse){
            foreach($t in $TeamCreationResponse){
                $newTeamDisplayName = $t.displayName
                $TeamId = $t.Id
                Write-Host “Team $newTeamDisplayName has been created successfully…” -ForegroundColor Green
               
            }
           
           
            Write-Host “Adding Channels to $newTeamDisplayName Team…” -ForegroundColor Yellow
            $TeamsChannelsUrl = “$GraphURL/teams/$TeamId/channels”
               
            try
            {
                $arrteamchannels = $TeamChannels -split “#”
                if($arrteamchannels)
                {
                    for($i =0; $i -le ($arrteamchannels.count – 1) ; $i++)
                    {
                        $ChannelName = $arrteamchannels[$i]
                        $ChannelDescription = “Channel 1 Description”
                        $body = [ordered]@{
                            displayName = $ChannelName;
                            description = $ChannelDescription;
                        }
                        $bodyJSON = $body | ConvertTo-Json 
                        Invoke-RestMethod -Uri $TeamsChannelsUrl -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
                        Write-Host ”      Channel $ChannelName is now added to the Team.” -ForegroundColor Green
                    }
                }
            }
            Catch
            {
                Write-Host “There is issue with Channel settings in CSV, Check and Fix:. $teamchannels”
            }
           
            $GeneralChannelsUrl = “$TeamsChannelsUrl” + “?`$filter=displayName eq ‘General’&`$select=Id”
            $GeneralChannelResponse = Invoke-RestMethod -Uri $GeneralChannelsUrl -Headers @{Authorization = “Bearer $token”} -Method Get -Verbose 
            $ChannelID = $GeneralChannelResponse.value[0].id;
            $GeneralChannelMessageUrl = “$TeamsChannelsUrl/$ChannelID/messages”
           
            $Message = “<H1>Welcome to Microsoft Teams From DXC Technology</H1>”
            $RootMessage = @{}
            $MessageBody = @{}
            $MessageBody.Add(“ContentType”,1)
            $MessageBody.Add(“content”,$Message)
            $RootMessage.Add(“body”,$MessageBody)
           
            $body = [ordered]@{
                rootMessage = $RootMessage;
            }
            $bodyJSON = $body | ConvertTo-Json 
            Invoke-RestMethod -Uri $GeneralChannelMessageUrl -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
            Write-Host ”      Message has been posted on the Channel.” -ForegroundColor Green
           
            #Adding Tab in General
            $TeamsUrl = “$GraphURL/teams/$TeamID”
            $TeamsAppsUrl = “$TeamsUrl/installedApps”
           
            $TabConfiguration = @{}
            $TabConfiguration.Add(“entityId”,$null)
            $TabConfiguration.Add(“contentUrl”,”https://www.bing.com/maps/embed?h=768&w=800&cp=39.90073511625853~-75.16744692848968&lvl=18&typ=d&sty=h&src=SHELL&FORM=MBEDV8″)
            $TabConfiguration.Add(“websiteUrl”,”https://binged.it/2BqOkiG”)
            $TabConfiguration.Add(“removeUrl”,$null)
           
            $TabPath = $GraphURL + “/appCatalogs/teamsApps/com.microsoft.teamspace.tab.web”
            $body = [ordered]@{
                “name”=”Places to Go”
                “teamsApp@odata.bind”=$TabPath
                Configuration = $TabConfiguration;
            }
           
            $bodyJSON = $body | ConvertTo-Json 
            $GeneralChannelTabs = “$TeamsChannelsUrl/$ChannelID/tabs”
           
            Invoke-RestMethod -Uri $GeneralChannelTabs -Headers @{Authorization = “Bearer $token”} -Body $bodyJSON -Method Post -ContentType “application/json” -Verbose
            Write-Host ”      Message has been posted on the Channel.” -ForegroundColor Green
        }
     
    }

}

Export Microsoft Teams Information using Graph API


As we already know that Microsoft Teams PowerShell does not provide cmdlets to get detailed information from Microsoft Teams.  It may evolve over next few months but we do have Microsoft Graph API that provides new has endpoints that you can use to export information about Team, Channels, Messages, Members and owners etc.  The sample below does some of the basic functions.  It all the teams and then try to get some information to an array of objects.  This is no way a complete sample but a good starting point.   It might help you get started with Teams.

I am using PNPOnline Powershell Module to get connect to Micorosft Graph and Get the Access token.  There couple of different ways to do that but PNP makes it lot easier.  Make sure you install the SharePointPnPPowerShellOnline module.

You can create an App in Azure Active Directory that has the Group.Read.all and User.Read.All permissions to make it easy for you.  Make sure you click on Grant permission after adding the Graph API permission.

$scopes = @(“Group.Read.All”,”User.ReadWrite.All”, “Directory.Read.All”,”Reports.Read.All”)
$scopes = $null

$ApplicationID = “”
$Password = ”
$appaaddomain = ‘tenant.onmicrosoft.com’
 
$GraphURL = “https://graph.microsoft.com/beta”
$url = “$GraphURL/groups?`$filter=resourceProvisioningOptions/Any(x:x eq ‘Team’)”

#Establish connection
If($scopes.Length -gt 0){
    Connect-PnPOnline -Scopes $scopes
} elseif($ApplicationID.Length -gt 0) {
    Write-Host “Connecting using Application” -ForegroundColor Yellow
    Connect-PnPOnline -AppId $ApplicationID -AppSecret $Password -AADDomain $appaaddomain
} else {
    write-host ‘Connection issue’ -ForegroundColor Red
    exit
}
 
#Get token
$token = Get-PnPAccessToken
 
#Call graph
if($token){
    $response = Invoke-RestMethod -Uri $url -Headers @{Authorization = “Bearer $token”}
} else {
    write-host ‘Token issue’ -ForegroundColor Red
    exit
}

$TeamsData = $null
$TeamsData = @()

#Parse data
if($response){
    foreach($r in $response.value){
        if($r.resourceProvisioningOptions -eq ‘Team’){
           
            $teamDataObj = New-Object -TypeName PSObject
           
            $GroupClassification = $r.classification
            $GroupCreatedDateTime = $r.createdDateTime
            $visibility = $r.visibility
            $TeamName = $r.displayname
            write-host “Team Name:$TeamName Classification:$GroupClassification CreatedOn:$GroupCreatedDateTime” -ForegroundColor Yellow
            $TeamID = $r.id
            $TeamUrl =  “$GraphURL/groups/$TeamID/members”
            $TeamsOwnerUrl =  “$GraphURL/groups/$TeamID/owners”
            $ChannelsUrl =  “$GraphURL/groups/$TeamID/channels”
           
            $teamDataObj | Add-Member -Type NoteProperty -Name TeamName -Value $TeamName
            $teamDataObj | Add-Member -Type NoteProperty -Name Createdon -Value $GroupCreatedDateTime
            $teamDataObj | Add-Member -Type NoteProperty -Name Classification -Value $GroupClassification
            $teamDataObj | Add-Member -Type NoteProperty -Name Visibility -Value $visibility
           
            if($token){
                $rTeam = Invoke-RestMethod -Uri $TeamUrl -Headers @{Authorization = “Bearer $token”}
                $rTeamsOwners = Invoke-RestMethod -Uri $TeamsOwnerUrl -Headers @{Authorization = “Bearer $token”}
                $rTeamChanel = Invoke-RestMethod -Uri $ChannelsUrl -Headers @{Authorization = “Bearer $token”}
            } else {
                write-host ‘Token issue’ -ForegroundColor Red
                exit
            }
            $TeamOwnersAll = “”
            if($rTeamsOwners)
            {
                Write-Host ”    Team Owners:”
                foreach($owner in $rTeamsOwners.value)
                {
                    $OwnerName = $owner.displayName
                    $OwnerEmail = $owner.mail
                    Write-Host ”        $OwnerName”
                    $TeamOwnersAll +=”$OwnerEmail#”
                }
         
            }
            $teamDataObj | Add-Member -Type NoteProperty -Name Owners -Value $OwnerEmail
            if($rTeam)
            {
                $TeamMembersAll = “”
                Write-Host ”    Team Members:”
                foreach($member in $rTeam.value)
                {
                    $TeamMember = $member.displayName
                    $TeamMemberEmail = $member.mail
                    $TeamMemberRole = $member.assignedLicenses
                    Write-Host ”        $TeamMember”
                    $TeamMembersAll +=”$TeamMemberEmail#”
                }
         
            }
            $teamDataObj | Add-Member -Type NoteProperty -Name TeamMembers -Value $TeamMembersAll
            if($rTeamChanel)
            {
                Write-Host ”    Channels”
                $AllChannels = “”
                foreach($channel in $rTeamChanel.value)
                {
                    $ChannelName = $channel.displayName
                    $ChannelId = $channel.id
                    Write-Host ”        Channels: $ChannelName”
                    $AllChannels +=”$ChannelName#”
                   
           
                    #/teams/{id}/channels/{id}/messages
           
                    $ChannelsMessageUrls =  “$GraphURL/teams/$TeamID/channels/$ChannelId/messages”
           

                    if($token){
                        $rMessages = Invoke-RestMethod -Uri $ChannelsMessageUrls -Headers @{Authorization = “Bearer $token”}
                    } else {
                        write-host ‘Token issue’ -ForegroundColor Red
                        exit
                    }
                   
                    $MessageCount = 0
                    $Replies = 0
           
                    if($rMessages)
                    {
                        foreach($Message in $rMessages.value)
                        {
                            $MessageId = $Message.id
                            $messageBody = $Message.body
                            $MessagecreatedDateTime = $message.createdDateTime
                            $MessageCount++;
                            Write-Host ”        Message: $messageBody $MessageId $MessagecreatedDateTime”
                            $ChannelsRepliesUrls =  “$GraphURL/teams/$TeamID/channels/$ChannelId/messages/$MessageId/replies”
                            if($token){
                                $rReplies = Invoke-RestMethod -Uri $ChannelsRepliesUrls -Headers @{Authorization = “Bearer $token”}
                                if($rReplies)
                                {
                                    foreach($rReplie in $rReplies.Value)
                                    {
                                        $rreplyId = $rReplie.body
                                        $rcreatedDateTime = $rReplie.createdDateTime
                                        $Replies++;
                                        Write-Host ”            Reply:$rcreatedDateTime”
                                    }
                                }
                            } else {
                                write-host ‘Token issue’ -ForegroundColor Red
                                exit
                            }
                        }
                    }
       
                }
                $teamDataObj | Add-Member -Type NoteProperty -Name Channels -Value $AllChannels
                $teamDataObj | Add-Member -Type NoteProperty -Name MessageCount -Value $MessageCount
                $teamDataObj | Add-Member -Type NoteProperty -Name RepliesCount -Value $Replies
            }
           
            $TeamsData += $teamDataObj
       
            #Do fancy stuff in here
        } else {
            write-host $r.displayname “is a regular O365 Group” -ForegroundColor Green
        }
    }
} else {
    write-host ‘Response issue’ -ForegroundColor Red
}

$TeamsData | Out-GridView
$TeamsData | Export-Csv -Path C:\temp\TeamsInfo.csv –NoTypeInformation

Technet Link for Code.

https://gallery.technet.microsoft.com/Export-Teams-Information-2ea6b3db

Get List of Modern Pages from SharePoint Online Sites


The script uses a list of all sites from SharePoint Online.  Check my earlier post Get all Sites and Subsites from SharePoint Online Sites to get the list of all sites.

$User = “admin@M365x086769.onmicrosoft.com”
#$User = Read-host “Please enter Office365 Admin User name username@domain.onmicrosoft.com. “
$File = “C:\365Creds\AllSites.csv”
$Creds = Get-Credentials

$SiteModernPages = @()
$CSVPath = “C:\temp\AllSites.csv”
$Data = Import-Csv -Path $CSVPath
if($Data)
{
    foreach($Site in $Data)
    {
        try
        {
            Connect-PnPOnline -Url $Site.SiteUrl -Credentials $Creds
            Write-Host “Working on $($Site.SiteUrl).”
            $items = $null
            $items = Get-PnPListItem -List “SitePages” -Fields ID,Title,BannerImageUrl,FileRef -ErrorAction SilentlyContinue
            if($items)
            {
                foreach($ModernPage in $items)
                {
                    Write-Host “.” -NoNewline
                    if($ModernPage[“BannerImageUrl”].Url -ne $null)
                    {
                        Write-Host “.” -NoNewline
                        $FileName = $ModernPage[“FileLeafRef”]
                        $PageUrl = “$($Site.SiteUrl)/SitePages/$FileName”
                        $PageObject = [PSCustomObject]@{Site = $Site.SiteUrl; PageTitle = $ModernPage[“Title”]; PageUrl = $PageUrl  }
                        $SiteModernPages +=$PageObject
                        Write-Host “Modern Page Found.  Added to List” -ForegroundColor Green
                    }
                }
            }
       
            Disconnect-PnPOnline

        }
        catch
        {
            “Error was $_”
            $line = $_.InvocationInfo.ScriptLineNumber
            “Error was in Line $line”
        }

    }
}

#Export All Pages to CSV
$SiteModernPages | Export-Csv -Path C:\temp\SitesModernPages.csv -NoTypeInformation -Force
#Export Page Count by Site to CSV
$SiteModernPages | group Site | Select Name, Count | Export-Csv -Path C:\temp\SitesModernPagesCount.csv -NoTypeInformation –Force

Technet Link

https://gallery.technet.microsoft.com/Get-List-of-Modern-Pages-9ae35436

Change/Add Office 365 Group Email using Office 365 PowerShell


$Creds = Get-Credential -Message “Please enter Office 365 Global Admin Credentials”
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Creds -Authentication Basic -AllowRedirection
Import-PSSession $Session

$AllGroups = Get-UnifiedGroup -Filter *

#Getting first group just for checking. 
$FirstGroup = Get-UnifiedGroup -Identity $AllGroups[0].Id

#Syntax

#{Add=”<Type>:<emailaddress1>”,”<Type>:<emailaddress2>”,…; Remove=”<Type>:<emailaddress2>”,”<Type>:<emailaddress2>”}.

$EmailAdded = @{Add=”smtp:bigwigstest@sharepointmvp.onmicrosoft.com”}
Set-UnifiedGroup -Identity $FirstGroup.Id -EmailAddresses $EmailAdded -Verbose

Get all Sites and Subsites from SharePoint Online Sites


$User = “admin@M365x086769.onmicrosoft.com”
$Creds = Get-Credentials

$AllSites = @()
function Get-SPOSubWebs{
    Param(
        [Microsoft.SharePoint.Client.ClientContext]$Context,
        [Microsoft.SharePoint.Client.Web]$RootWeb
    )
 
 
    $Webs = $RootWeb.Webs
    $Context.Load($Webs)
    $Context.ExecuteQuery()
 
    ForEach ($sWeb in $Webs)
    {
        Write-Output $sWeb
        $SiteObject = [PSCustomObject]@{Site = $sWeb.Url }
        $AllSites +=$SiteObject
        Get-SPOSubWebs -RootWeb $sWeb -Context $Context
    }
}

Connect-SPOService -Url “https://M365x086769-admin.sharepoint.com” -Credential $Creds
$Sites = Get-SPOSite -Limit All | Select Url
foreach($Site in $Sites)
{
   
    Set-SPOUser -Site $Site.Url -LoginName $Creds.UserName -IsSiteCollectionAdmin $true -Verbose
    $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Creds.UserName, $Creds.Password)
    $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($Site.Url)
    $ctx.Credentials = $credentials
    $Web = $ctx.Web
    $ctx.Load($Web)
    $ctx.ExecuteQuery()
    $SiteObject = [PSCustomObject]@{SiteUrl = $Web.Url }
    $AllSites +=$SiteObject
    Get-SPOSubWebs -RootWeb $Web -Context $ctx   
}

$AllSites

$AllSites | Export-Csv -Path C:\temp\AllSites.csv -NoTypeInformation –Force

Link on Technet

https://gallery.technet.microsoft.com/Get-all-Sites-and-Subsites-a0972bdb

Export SharePoint Online Site Collection Information to SQL Server


There is easy way to collection reports when you have large number of Site collections on SharePoint Online.  The new Admin center will help but sometime customers have external system where they want to review and control site collection information.  The script below will iterate site collections, sites, lists and permissions and store them to a SQL Server Database.  It uses SharePoint Online Client components so you must downloaded and install the latest version of client Components from PowerShell Gallary using the method below

https://www.nuget.org/packages/Microsoft.SharePointOnline.CSOM

Download nuget command line (I saved it to c:\nuget)

    Command Prompt: 

    1. cd C:\nuget
    2. nuget.exe install Microsoft.SharePointOnline.CSOM

    Powershell (elevated)

    1. cd C:\nuget
    2. Install-Package -Name ‘Microsoft.SharePointOnline.CSOM’ -Source .\Microsoft.SharePointOnline.CSOM.16.1.8412.1200
    3. Import-Module ‘C:\nuget\Microsoft.SharePointOnline.CSOM.16.1.8412.1200\lib\net45\Microsoft.SharePoint.Client.dll

    https://gallery.technet.microsoft.com/Export-SharePoint-Online-27920e67