I am back!!! I think.


I am back…. I think so. It has been long time since I blogged anything useful other than the tons of PowerShell I have been posting. Finally I thought about changing the blogging model. Instead of blogging text I will be logging videos. The topics will be mostly random based on what I am mostly working during the day.

My work does not have a pattern. I start my day with Power Automate Flow followed by PowerShell script, later a Power App and could be updating classic AngularJS solutions built on top of SharePoint and by end of day I could be writing or deploying an SPFX Web Part for my customers. This put me in unique position.

What’s going on for me

First of all I updated my work environment. Moved my table to a position that is facing the wall finally :). Bought a Ultrawide 49 inch 5K display and setup my Lenovo P53 machine with I9 processor. The feeling is pretty good. Check below.

Dell Ultrawide 5K Display

Microsoft MVP for Another Year

The next news is that I was renewed as Microsoft MVP – Office Apps and Services for another year. It is a great honor as I am not an MVP for 11 Years. I must be the first MVP from Pakistan to to pass the 10 year mark to get this award.

Pakistani MVP Meetup

Just last week, after months of planning 🙂 I was able to get hold of few Pakistani MVPs to do a quick meeting. 3 out of 5 were able to join from 3 different regions of the world. It was fun to talk to them. I am sharing the video. We did not talk allot about technology as it as mostly a friendly chitchat (and Yes, I was not looking at the Camera. My web cam just few minutes before the call so I had to use my laptop camera). You can check it out below.

That’s all for now. looking forward to the next video.

Jerry Yasir

Microsoft Power Automate – Avoid Loop on SharePoint Item Update


There might be situations where we will have to update a SharePoint list inside Microsoft Power Automate Flow. When we run the flow manually on list items then the workflow runs without any issue. But if we perform the update inside When Item is Created or Update trigger, the flow goes to a not ending loop. In the video below I have tried to cover this scenario.

using call the SharePoint Rest service

In the 2nd part of the video, I have shown the method of using the call the https service for SharePoint using the SharePoint REST service. This method is very suitable when you have large number of columns but want to update only few columns. Check it below

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
         }
      
     }

}

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

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