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

    Add Custom Banner to SharePoint Site without changing master page


    Few days ago, I got a question if it is possible to add a banner message to a SharePoint Online or On-premises site without customizing the master page.  They were looking for a way to enable and disable this using the Feature framework so I had to write a feature event receiver to upload the customized master page.  The customer was happy but I was not.

    I did some research and ended up finding something that makes it lot easier to do such things.  You can do it using Code using the event receiver but I wanted to make sure you do not deploy any server side solution.  The script below using CSOM to get things done.  All you need is the jquery file and the custom js file uploaded to a CDN or SharePoint assets library.  If you are running off the server then you need to deploy SharePoint client side assemblies from Microsoft download center.

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

    function Add-SPBanner($SiteUrl, $Credentials)
    {
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
    $context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credentials.UserName,$Credentials.Password)
    $site = $context.Web
    $context.Load($site)
    $context.ExecuteQuery()

    $UserCustomActions = $site.UserCustomActions
    $context.Load($UserCustomActions)
    $context.ExecuteQuery()

    $newAction = $UserCustomActions.Add()
    $newAction.Location = “ScriptLink”
    $newAction.scriptSrc = “~SiteCollection/Style Library/jquery.min.js”
    $newAction.Sequence = 30000
    $newAction.Title= “BannerJquery”
    $newAction.Update()
    $context.ExecuteQuery()

    #add custom js injection action
    $customJSAction = $UserCustomActions.Add()
    $customJSAction.Location = “ScriptLink”
    #reference to JS file
    $customJSAction.ScriptSrc = “~SiteCollection/Style Library/test.js”
    #load it last
    $customJSAction.Title= “BannerJS”
    $customJSAction.Sequence = 30001
    #make the changes
    $customJSAction.Update()
    $context.ExecuteQuery()

    Write-Host “Banner has been Added…” -ForegroundColor Green
    }

    function Remove-SPBanner($SiteUrl, $Credentials)
    {
    $context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteUrl)
    $context.Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Credentials.UserName,$Credentials.Password)
    $site = $context.Web
    $context.Load($site)
    $context.ExecuteQuery()

    $UserCustomActions = $site.UserCustomActions
    $context.Load($UserCustomActions)
    $context.ExecuteQuery()

    $UserCustomActions | ? Title -Like “Banner*” | Select Title, Sequence
    if($UserCustomActions.Count -gt 0)
    {
    $CA = $UserCustomActions | ? Title -eq “BannerJquery”
    $CA.DeleteObject()

    $CA = $UserCustomActions | ? Title -eq “BannerJS”
    $CA.DeleteObject()
    $context.ExecuteQuery()
    Write-Host “Banner has been Removed…” -ForegroundColor Green
    }
    }

    $Creds = Get-Credential
    $SiteUrl = “https://sharepointmvp.sharepoint.com”

    Add-SPBanner -SiteUrl $SiteUrl -Credentials $Creds
    Remove-SPBanner -SiteUrl $SiteUrl -Credentials $Creds

    Code on Technet

    https://gallery.technet.microsoft.com/Add-Custom-Banner-to-dbbf22d3

    Create SharePoint Online Site Inventory using CSOM


    There is no simply method available in SharePoint Online to see the structure of the SharePoint sites.  The script below tries to get some inventory which may help you may be during the migration or may be documentation of SharePoint Online Sites.  The script iterates all sites and sub sites. The script uses Basic Client Side Object Model components.  I suggest you download the latest Components from GitHub.

    $Creds = Get-Credential
    $Creds.UserName
     
    Connect-SPOService -Url https://tenant-Admin.sharepoint.com -Credential $Creds
    Connect-MsolService -Credential $Creds
    $Users = Get-MsolUser -All
    $UnLicensedUsers = Get-MsolUser -UnlicensedUsersOnly
    $Users.Count
    $UnLicensedUsers.Count
    $Users.Count – $UnLicensedUsers.Count
     
    Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll”
    Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll”
     
    $SitesIncludingPersonal = Get-SPOSite -IncludePersonalSite $true -Limit All -Detailed
    $SitesIncludingPersonal | Select * | Export-Csv -Path C:\temp\PondSites1.csv
    $Sites = Get-SPOSite -Limit All -Detailed
    foreach($asite in $Sites)
    {
      Set-SPOUser -Site $asite.Url -LoginName $Creds.UserName -IsSiteCollectionAdmin $true -ErrorAction SilentlyContinue
    }
    $Sites | Export-Csv -Path C:\temp\PondSites.csv -NoClobber -NoTypeInformation
    foreach($asite in $Sites)
    {
      $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($asite.Url)
      #Authenticate
      $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Creds.UserName , $Creds.Password)
      $ctx.Credentials = $credentials
     
      #Fetch the users in Site Collection
      $ctx.Load($ctx.Web.Webs)
      $ctx.ExecuteQuery()
      $Count=0
      foreach($aWEb in $ctx.Web.Webs)
      {
        $Count++;
      }
      Write-Host $asite.Url $Count
    }
     
    $Databases = $null
    $Databases = @();
    foreach($aSite in $Sites)
    {
      $Users = Get-SPOUser -Site $aSite.Url -Limit All | Select * -Verbose
      foreach($User in $Users)
      {
        $DB = New-Object PSObject
        Add-Member -input $DB noteproperty ‘SiteUrl’ $aSite.Url 
        Add-Member -input $DB noteproperty ‘DisplayName’ $User.DisplayName
        Add-Member -input $DB noteproperty ‘LoginName’ $User.LoginName
        Add-Member -input $DB noteproperty ‘IsSiteAdmin’ $User.IsSiteAdmin
        Add-Member -input $DB noteproperty ‘IsGroup’ $User.IsGroup
        $Databases += $DB
      }
    }
    $UsersOutput = “C:\temp\AllSitesUsers.csv”
    $Databases | Export-Csv -Path $UsersOutput -NoTypeInformation -Force
                                                                          
     
    $Databases = $null
    $Databases = @();
    foreach($asite in $Sites)
    {
      $Groups = Get-SPOSiteGroup -Site $asite.Url -Limit 100 | Select *
      foreach($Group in $Groups)
      {
        $DB = New-Object PSObject
        Add-Member -input $DB noteproperty ‘SiteUrl’ $aSite.Url 
        Add-Member -input $DB noteproperty ‘DisplayName’ $Group.Title
        Add-Member -input $DB noteproperty ‘LoginName’ $Group.LoginName
        Add-Member -input $DB noteproperty ‘OwnerLoginName’ $Group.OwnerLoginName
        Add-Member -input $DB noteproperty ‘OwnerTitle’ $Group.OwnerTitle
        $RolesString = “”
        foreach($Role in $Group.Roles)
        {
          $RolesString+=$Role
          $RolesString+=”,”
        }
        Add-Member -input $DB noteproperty ‘Roles’ $RolesString
        $Databases += $DB
      }
    }
     
    $UsersOutput = “C:\temp\AllSiteGroups.csv”
    $Databases | Export-Csv -Path $UsersOutput -NoTypeInformation -Force
     
    $Databases = $null
    $Databases = @();
    foreach($asite in $Sites)
    {
      $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($asite.Url)
      #Authenticate
      $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Creds.UserName , $Creds.Password)
      $ctx.Credentials = $credentials
     
      #Fetch the users in Site Collection
      $ctx.Load($ctx.Web.Webs)
      $Lists = $ctx.Web.Lists
      $ctx.Load($Lists)
      $ctx.ExecuteQuery()
      foreach($List in $Lists)
      {
        if($List.Hidden -eq $false)
        {
          if($List.ItemCount -gt 100)
          {
            $DB = New-Object PSObject
            Add-Member -input $DB noteproperty ‘SiteUrl’ $asite.Url 
            Add-Member -input $DB noteproperty ‘Title’ $List.Title
            Add-Member -input $DB noteproperty ‘ListType’ $List.BaseType
            Add-Member -input $DB noteproperty ‘ItemCount’ $List.ItemCount
            $Databases += $DB
            Write-Host $aSite.Url $List.Title $List.ItemCount
          }
          
        }
      }
     
      foreach($aWeb in $ctx.Web.Webs)
      {
        $Lists = $aWeb.Lists
        $ctx.Load($Lists)
        $ctx.ExecuteQuery()
        foreach($List in $Lists)
        {
          if($List.Hidden -eq $false)
          {
            if($List.ItemCount -gt 100)
            {
              $DB = New-Object PSObject
              Add-Member -input $DB noteproperty ‘SiteUrl’ $aWeb.Url 
              Add-Member -input $DB noteproperty ‘Title’ $List.Title
              Add-Member -input $DB noteproperty ‘ListType’ $List.BaseType
              Add-Member -input $DB noteproperty ‘ItemCount’ $List.ItemCount
              $Databases += $DB
              Write-Host $aSite.Url $List.Title $List.ItemCount
            }
          
          }
        }
      }
      Write-Host $asite.Url
    }
     
    $UsersOutput = “C:\temp\Libraries.csv”
    $Databases | Export-Csv -Path $UsersOutput -NoTypeInformation -Force
    $Databases | Out-GridView
     
     
    $Databases = $null
    $Databases = @();
    foreach($asite in $Sites)
    {
      #$asite = “https://leapthepond.sharepoint.com”
      $ctx = New-Object Microsoft.SharePoint.Client.ClientContext($asite.url)
      #Authenticate
      $credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($Creds.UserName , $Creds.Password)
      $ctx.Credentials = $credentials
     
      #Fetch the users in Site Collection
      $ctx.Load($ctx.Web.Webs)
      $Lists = $ctx.Web.Lists
      $ctx.Load($Lists)
      $ctx.ExecuteQuery()
      foreach($List in $Lists)
      {
        if($List.Hidden -eq $false)
        {
          $ctx.Load($List)
          $ctx.ExecuteQuery()
          if($List.WorkflowAssociations.Count -gt 0)
          {
            $DB = New-Object PSObject
            Add-Member -input $DB noteproperty ‘SiteUrl’ $asite.Url 
            Add-Member -input $DB noteproperty ‘Title’ $List.Title
            Add-Member -input $DB noteproperty ‘ListType’ $List.BaseType
            Add-Member -input $DB noteproperty ‘WorkflowsCount’ $List.WorkflowAssociations.Count
            $Databases += $DB
            Write-Host $List.Title $List.ItemCount
          }
          
        }
      }
     
      foreach($aWeb in $ctx.Web.Webs)
      {
        $Lists = $aWeb.Lists
        $ctx.Load($Lists)
        $ctx.ExecuteQuery()
        foreach($List in $Lists)
        {
          if($List.Hidden -eq $false)
          {
            if($List.ItemCount -gt 100)
            {
              $DB = New-Object PSObject
              Add-Member -input $DB noteproperty ‘SiteUrl’ $aWeb.Url 
              Add-Member -input $DB noteproperty ‘Title’ $List.Title
              Add-Member -input $DB noteproperty ‘ListType’ $List.BaseType
              Add-Member -input $DB noteproperty ‘WorkflowsCount’ $List.WorkflowAssociations.Count
              $Databases += $DB
              Write-Host $List.Title $List.ItemCount
            }
          
          }
        }
      }
      Write-Host $asite.Url
    }
     
    $UsersOutput = “C:\temp\Libraries.csv”
    $Databases | Export-Csv -Path $UsersOutput -NoTypeInformation -Force
    $Databases | Out-GridView

    Bulk Upload Files to SharePoint Online with Metadata


    There are many scripts on Technet that helps you upload the content from File Shares to SharePoint Online or OneDrive for Business but none of them actual copy the user information from file shares.  The script below migrates metadata like created by and created and modificed dates along with the files.  The script uses SharePoint Online Client Side Object Model.  I hope this helps someone.  

    Note: The actual script was written by someone at Technet which I do not remember, I just made serious modifications to make it possible to extract and upload metadata.

    [CmdletBinding()]
    param(
      [Parameter(Mandatory=$True,Position=1)]
      [String]$UserName,
      [Parameter(Mandatory=$True,Position=2)]
      [String]$Password,
      [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)]
      [string]$MetaDataCSV,
      [Parameter(Mandatory=$False, Position=8)]
      [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\16\ISAPI\Microsoft.SharePoint.Client.dll”
    Add-Type -Path “C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll”

    function GetUserLookupString{
        [CmdletBinding()]
        param($context, $userString)
       
        try{
            $user = $context.Web.EnsureUser($userString)
            $context.Load($user)
            ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
           
            # 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)’.”
            $userLookupString = $null
        }
       
        return $userLookupString
    }

    function ExecuteQueryWithIncrementalRetry([Microsoft.SharePoint.Client.ClientContext] $context, $retryCount, $delay)
    {
        if ($retryCount -eq $null) { $retryCount = 5 } # default to 5
        if ($delay -eq $null) { $delay = 500 } # default to 500
        $retryAttempts = 0
        $backoffInterval = $delay
        if ($retryCount -le 0)
        {
            throw New-Object ArgumentException(“Provide a retry count greater than zero.”)
        }

        if ($delay -le 0)
        {
            throw New-Object ArgumentException(“Provide a delay greater than zero.”)
        }

        # Do while retry attempt is less than retry count
        while ($retryAttempts -lt $retryCount)
        {
            try
            {
                $context.ExecuteQuery()
                return
            }
            catch [System.Net.WebException]
            {
                $response = [System.Net.HttpWebResponse]$_.Exception.Response
                # Check if request was throttled – http status code 429
                # Check is request failed due to server unavailable – http status code 503
                if ($response -ne $null -and ($response.StatusCode -eq 429 -or $response.StatusCode -eq 503))
                {
                    # Output status to console. Should be changed as Debug.WriteLine for production usage.
                    Write-Host “CSOM request frequency exceeded usage limits. Sleeping for $backoffInterval seconds before retrying.”

                    # Add delay for retry
                    Start-Sleep -m $backoffInterval

                    # Add to retry count and increase delay.
                    $retryAttempts++
                    $backoffInterval = $backoffInterval * 2
                }
                else
                {
                    throw
                }
            }
        }
        throw New-Object Exception(“Maximum retry attempts $retryCount, has be attempted.”)
    }

    <#
        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()
            ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
            if($Upload.CheckOutType -ne “none”)
            {
                $Upload.CheckIn(“Checked in by Administrator”, [Microsoft.SharePoint.Client.CheckinType]::MajorCheckIn)
            }
        }
        $Context.Load($Upload)
        #$Context.ExecuteQuery()
        ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
       
        if($MetaDataCSVData)
        {
          $MetaDataItem = $MetaDataCSVData | ? FilePath -eq $File.FullName
          Write-Host “User does not exist in metadata file.  Standard metadata will be used…” -ForegroundColor Yellow
          if($MetaDataItem)
          {
            Write-Host “Adding Metadata to File Item…$($File.FullName)”
            $listItem = $Upload.ListItemAllFields
            $Context.Load($listItem)
            #$Context.ExecuteQuery()
            ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
       
            $Item = $List.GetItemById($listItem.Id);
            $Context.Load($Item)
            #$Context.ExecuteQuery()
            ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
         
            $Item[“Description0”] = $MetaDataItem.Description      
            $Author = GetUserLookupString $Context $MetaDataItem.Author
            if($Author)
            {
              $Item[“Author”] = $Author
            }
            $Editor = GetUserLookupString $Context $MetaDataItem.Editor
            if($Editor)
            {
              $Item[“Editor”] = $Editor
            }
           
            [System.DateTime]$CreatedOnDate = $MetaDataItem.CreatedOn
            [System.DateTime]$ModifiedOnDate = $MetaDataItem.ModifiedOn
            $Item[“Created”] = $CreatedOnDate
            $Item[“Modified”] = $ModifiedOnDate
            try
            {
              $Item.Update()
            }
            catch
            { Write-Host “Exception Occured… But script will continue” }
            #$Context.ExecuteQuery()
            ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
          }
        }
       
       
     
    }

    <#
        Create Folder Function.
    #>
    function PopulateFolder($ListRootFolder, $FolderRelativePath)
    {
        #split the FolderRelativePath passed into chunks (between the backslashes) so that we can check if the folder structure exists
        $PathChunks = $FolderRelativePath.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()
        ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500

        #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()
                ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500

            }
            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()
                ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
                $WorkingFolder= $WorkingFolder.folders.add($Chunk)
                $Context.load($WorkingFolder)
                $Context.load($WorkingFolder.folders)
                #$Context.ExecuteQuery()
                ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500
               
            }

        }

        #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($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)
    $Web = $Context.Web;
    $Context.Load($Web)
    $Context.Load($List)
    $List.EnableVersioning = $false
    $List.Fields.GetByInternalNameOrTitle(“Author”).ReadOnlyField = $false;
    $List.Fields.GetByInternalNameOrTitle(“Editor”).ReadOnlyField = $false;
    $List.Fields.GetByInternalNameOrTitle(“Created”).ReadOnlyField = $false;
    $List.Fields.GetByInternalNameOrTitle(“Modified”).ReadOnlyField = $false;
    $List.Update()
    #$Context.ExecuteQuery()
    ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500

    $MetaDataCSV = “C:\temp\Metadata.csv”
    $MetaDataCSVData = Import-Csv -Path $MetaDataCSV

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

    $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()
    ExecuteQueryWithIncrementalRetry -context $Context -retryCount 5 -delay 500

    Script is also available below

    https://gallery.technet.microsoft.com/Bulk-Upload-Files-to-b9ed3126

    Extract All User Properties from Active Directory to CSV


    Below script allows you to use CSV to extract profile properties from active directory and then export to CSV.

    Import-Module ActiveDirectory -ErrorAction SilentlyContinue
    $Data = Import-Csv -Path “C:\Scripts\NoSPProfileAccounts.csv”
    $Users = $null
    $Users = @();
    if($Data)
    {
        $Data | ForEach-Object { Write-Host $_.Account; $User = Get-ADUser -properties * -Filter “UserPrincipalName -eq ‘$($_.Account)'”;$Users +=$User;}
    }
    $Users | Export-Csv -NoTypeInformation -Path “C:\Scripts\Users.csv”
    $Users | Out-GridView