Friday, July 4, 2025

๐Ÿ—‚️ Migrating Files from SharePoint Online to OneDrive: Methods, Challenges & Solutions

Migrating files from SharePoint Online to OneDrive for Business is a common task for IT teams aiming to streamline collaboration, reorganize data, or decommission legacy sites. While the process can be straightforward with the right tools, it also presents several challenges that require careful planning and execution.


๐Ÿšš Migration Methods

1. SharePoint Migration Tool (SPMT)

  • Best for: Structured, bulk migrations.
  • Pros: Retains metadata, supports folder/site-level migration, provides logs.
  • Cons: Requires setup and may need scripting for complex scenarios.

2. OneDrive Sync App

  • Best for: Small-scale or manual migrations.
  • Pros: Easy to use, no special setup.
  • Cons: Not scalable, no metadata retention.

3. PowerShell Automation

  • Best for: Custom, automated migrations.
  • Pros: Highly flexible, supports scripting and scheduling.
  • Cons: Requires technical expertise.

4. Manual Download & Upload

  • Best for: Very small migrations.
  • Pros: Simple and direct.
  • Cons: Time-consuming, risk of data loss.

5. Third-Party Tools

  • Best for: Enterprise-grade migrations.
  • Pros: Advanced features, granular control, support for hybrid environments.
  • Cons: Licensing costs, learning curve.

⚠️ Common Challenges & ✅ Solutions

๐Ÿ”„ Challenge 1: Volume of Data (e.g., 1TB+)

  • Problem: Large migrations can be slow and error prone.
  • Solution: Use SPMT or third-party tools with throttling and retry mechanisms. Break migration into department-wise batches.

๐Ÿงฉ Challenge 2: Folder Structure Preservation

  • Problem: Losing hierarchy during migration can confuse users.
  • Solution: Pre-create folder structures in OneDrive using PowerShell or manually. Map SharePoint folders to OneDrive destinations in SPMT.

๐Ÿ” Challenge 3: Permissions and Access

  • Problem: OneDrive is personal; SharePoint is collaborative.
  • Solution: Use shared folders or Microsoft 365 Groups. Post-migration, assign access manually or via scripts.

๐Ÿ“„ Challenge 4: Metadata Loss

  • Problem: Manual methods don’t retain metadata (created by, modified date, etc.).
  • Solution: Use SPMT or third-party tools that preserve metadata during migration.

๐Ÿงช Challenge 5: File Integrity Verification

  • Problem: Files may be corrupted or incomplete.
  • Solution: Use SPMT logs, checksum tools, or spot-check files. Maintain a migration checklist.

๐Ÿ‘ฅ Challenge 6: User Confusion Post-Migration

  • Problem: Users may not know where files are or how to share them.
  • Solution: Conduct training sessions, provide documentation, and set up support channels.

๐Ÿ—‘️ Challenge 7: Decommissioning Old SharePoint Sites

  • Problem: Risk of deleting active or needed content.
  • Solution: Archive sites before deletion. Confirm with stakeholders and maintain backups.

✅ Post-Migration Checklist

  • ๐Ÿ” Verify file integrity and completeness.
  • ๐Ÿ” Review and assign permissions.
  • ๐Ÿ“˜ Train users on OneDrive usage and sharing.
  • ๐Ÿ—‘️ Archive or decommission old SharePoint sites.

๐Ÿ“Œ Final Thoughts

Migrating from SharePoint to OneDrive is more than just moving files—it’s about ensuring continuity, usability, and security. By understanding the challenges and applying the right solutions, organizations can achieve a smooth transition that empowers users and improves collaboration.

Friday, June 6, 2025

๐Ÿข Monitoring and Optimizing Microsoft 365 SharePoint Sites for Efficiency and Governance

 

๐Ÿ“Œ Introduction

As organizations increasingly rely on Microsoft 365 for collaboration and content management, SharePoint Online has become a central hub for storing documents, managing projects, and enabling teamwork. However, over time, the proliferation of sites and subsites can lead to inefficiencies, storage bloat, and governance challenges. Identifying inactive or underutilized sites is essential for maintaining a healthy digital workspace.

This article outlines a practical approach to auditing SharePoint Online environments, retrieving comprehensive site data, and presenting actionable insights to leadership.


๐Ÿ” Why Monitor SharePoint Site Activity?

  1. Optimize Storage Costs: Unused sites consume valuable storage, which can lead to additional licensing costs.
  2. Improve Performance: Reducing clutter enhances search performance and user experience.
  3. Strengthen Governance: Identifying orphaned or outdated sites helps enforce compliance and security policies.
  4. Support Decision-Making: Data-driven insights empower IT and business leaders to make informed decisions about site lifecycle management.

๐Ÿ› ️ How to Retrieve Site Data

Using PowerShell and Microsoft Graph API, IT administrators can extract detailed information about:

  • Site Collections and Subsites
  • Last Modified Dates
  • Storage Usage
  • Site Owners
  • Activity Metrics

A sample PowerShell script can automate this process, exporting the data into a CSV file for further analysis.

Here’s a PowerShell code snippet that retrieves comprehensive details of all SharePoint Online site collections and their subsites, including usage statistics and last modified dates. This is ideal for identifying inactive or underutilized sites:


# Connect to SharePoint Online Admin Center
Connect-SPOService -Url "https://yourtenant-admin.sharepoint.com"

# Get all site collections

$sites = Get-SPOSite -Limit All

# Prepare array to store site data

$siteData = @()

foreach ($site in $sites) {

    $siteUrl = $site.Url
    $lastModified = $site.LastContentModifiedDate
    $storageUsage = $site.StorageUsageCurrent
    $owner = $site.Owner

    # Connect to each site using PnP PowerShell

    Connect-PnPOnline -Url $siteUrl -Interactive

    # Get subsites

    $subsites = Get-PnPSubWeb -Recurse

    foreach ($subsite in $subsites) {

        $siteData += [PSCustomObject]@{
            SiteCollection = $siteUrl
            Subsite        = $subsite.Url
            Title          = $subsite.Title
            LastModified   = $subsite.LastItemModifiedDate
            StorageUsageMB = $storageUsage
            Owner          = $owner
        }
    }

    # Add root site info

    $siteData += [PSCustomObject]@{
        SiteCollection = $siteUrl
        Subsite        = $siteUrl
        Title          = $site.Title
        LastModified   = $lastModified
        StorageUsageMB = $storageUsage
        Owner          = $owner
    }
}

# Export to CSV

$siteData | Export-Csv -Path "M365_Site_Report.csv" -NoTypeInformation

๐Ÿ“Š Visualizing the Data

Once the data is collected, it can be transformed into a compelling report or dashboard using tools like:

  • Power BI: Create interactive dashboards with filters for site activity, storage usage, and ownership.
  • Excel: Use pivot tables and conditional formatting to highlight inactive or high-usage sites.

Key visuals might include:

  • Bar Charts: Top 10 sites by storage usage
  • Heat Maps: Sites not modified in the last 6–12 months
  • Pie Charts: Distribution of active vs inactive sites

✅ Recommendations for IT Teams

  • Establish Site Lifecycle Policies: Define rules for archiving or deleting inactive sites.
  • Automate Regular Audits: Schedule scripts to run monthly or quarterly.
  • Engage Site Owners: Notify owners of inactivity and provide options for cleanup or archiving.
  • Integrate with Governance Tools: Use Microsoft Purview or third-party tools for enhanced compliance tracking.

๐Ÿ“ˆ Conclusion

Proactive monitoring of SharePoint Online sites is not just a technical necessity—it’s a strategic imperative. By leveraging automation and visualization, IT teams can provide leadership with the insights needed to streamline operations, reduce costs, and maintain a secure and efficient digital workplace.

Monday, May 19, 2025

๐Ÿงน Clean Up SharePoint Online File Versions - 3 different variations

 Versioning in SharePoint Online is a powerful feature that allows teams to maintain historical copies of documents. However, over time, these versions can accumulate and consume significant storage space—especially in document libraries with frequent updates.

This article provides a step-by-step PowerShell script using the SharePointPnPPowerShellOnline module to clean up old versions of files in a specific folder within a document library—retaining only the latest 5 versions of each file.


๐Ÿ”ง Why This Is Useful

  • Storage Optimization: SharePoint libraries with thousands of old file versions can significantly inflate site storage.

  • Performance: Reducing version history helps improve performance in large libraries.

  • Targeted Cleanup: Instead of affecting the entire document library, you can limit cleanup to a specific folder.


๐Ÿ› ️ Prerequisites

Install-Module -Name SharePointPnPPowerShellOnline -Force
  • SharePoint Online site URL and access permissions to the library/folder.

  • PowerShell with administrative rights.

๐Ÿ“œ Script Overview

This script:

  1. Connects to the SharePoint Online site.

  2. Targets a specific folder in a document library.

  3. Retrieves all files in that folder (recursively).

  4. Keeps only the latest 5 versions of each file and deletes the rest.


๐Ÿ” PowerShell Script

# Install and Import the Module (if not already done)
Install-Module -Name SharePointPnPPowerShellOnline -Force
Import-Module SharePointPnPPowerShellOnline

# Variables
$SiteURL = "https://gks.sharepoint.com/sites/yoursite"
$ListName = "TestVersionsDocLib"
$FolderServerRelativeUrl = "/sites/yoursite/TestVersionsDocLib/TargetFolder"  # Change as needed

# Connect to SharePoint
Connect-PnPOnline -Url $SiteURL -UseWebLogin  # Use -Interactive if using modern auth

# Get PnP Context
$Ctx = Get-PnPContext

# Get all files in the specified folder recursively
$ListItems = Get-PnPListItem -List $ListName -PageSize 2000 -Query "<View Scope='RecursiveAll'><Query><Where><BeginsWith><FieldRef Name='FileRef'/><Value Type='Text'>$FolderServerRelativeUrl</Value></BeginsWith></Where></Query></View>" | Where { $_.FileSystemObjectType -eq "File" }

foreach ($Item in $ListItems) {
    $File = $Item.File
    $Versions = $File.Versions

    $Ctx.Load($File)
    $Ctx.Load($Versions)
    $Ctx.ExecuteQuery()

    Write-Host "Scanning File: $($File.Name) with $($Versions.Count) versions"

    if ($Versions.Count -gt 5) {
        # Keep latest 5, delete the rest
        $VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 5
        foreach ($version in $VersionsToDelete) {
            $version.DeleteObject()
        }

        $Ctx.ExecuteQuery()
        Write-Host "Deleted $($VersionsToDelete.Count) older versions of the file: $($File.Name)"
    }
}

๐Ÿ“ Example Folder Path

If your document library is called TestVersionsDocLib and the target folder is Invoices/2025, the relative URL should be:

/sites/yoursite/TestVersionsDocLib/Invoices/2025

✅ Output

The script will:

  • Display each file being scanned.

  • Show how many versions were found.

  • Confirm deletion of versions beyond the latest 5.

⚠️ Important Considerations

  • This script only affects a specific folder—not the whole document library.

  • Always test in a development or QA site before using in production.

  • Deleting versions is irreversible—ensure you retain what’s necessary.


$SiteURL = "https://tc.sharepoint.com/teams/GK/ms"
$FolderSiteRelativeUrl = "Shared Documents/TargetTest"
 Connect-PnPOnline -Url $SiteURL -UseWebLogin
 # Test folder access
$Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
Write-Host "Folder found: $($Folder.Name)"
# Get files
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder"

You can test if the folder exists using this:

Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents"
Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents/4. Projects - WIP"
Get-PnPFolder -FolderSiteRelativeUrl "Shared Documents/4. Projects - WIP/FY'24"

 Get-PnPFolder -FolderSiteRelativeUrl "Shared%20Documents%2F04%2E%20Projects%20%2D%20WIP"

Get-PnPFolder -FolderSiteRelativeUrl "Shared%20Documents%2F04%2E%20Projects%20%2D%20WIP%2FFY%2724%2FFY%2724%20%2D%20Cancellation%20Reason%20%26%20Subreason"

 This helps isolate where the path is breaking.

#PS_Script_for Recursive Folder and Subfolders Tested

$SiteURL = "https://tc.sharepoint.com/teams/gks/SE"
$FolderSiteRelativeUrl = "Shared Documents/04. Projects - WIP/FY'24"
# Connect to SharePoint site
Connect-PnPOnline -Url $SiteURL -UseWebLogin
# Verify folder exists
try {
    $Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
    Write-Host "Folder found: $($Folder.Name)"
} catch {
    Write-Host "Error: Folder not found at '$FolderSiteRelativeUrl'"
    exit
}
# Get all files recursively
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder and subfolders."
foreach ($File in $Files) {
    try {
        $FileRef = $File.ServerRelativeUrl
        $FileItem = Get-PnPFile -Url $FileRef -AsListItem
        $Ctx = Get-PnPContext
        $SPFile = $FileItem.File
        $Versions = $SPFile.Versions

        # Load file and versions
        $Ctx.Load($SPFile)
        $Ctx.Load($Versions)
        $Ctx.ExecuteQuery()
        Write-Host "`nScanning File: $($SPFile.Name) - Total Versions: $($Versions.Count)"
        if ($Versions.Count -gt 2) {
            # Sort by created date (descending), skip latest 2
            $VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 2

            foreach ($Version in $VersionsToDelete) {
                Write-Host "Deleting Version: ID=$($Version.ID), Created=$($Version.Created)"
                $Version.DeleteObject()
            }
            # Commit deletion
            $Ctx.ExecuteQuery()
            Write-Host "Deleted $($VersionsToDelete.Count) older versions of: $($SPFile.Name)"
        } else {
            Write-Host "Skipping: Less than or equal to 3 versions present."
        }
    } catch {
        Write-Host "Error processing file '$($File.ServerRelativeUrl)': $_"
    }
}

PS_Script_ to delete the file versions on Entire Doc Library Tested
$SiteURL = "https://tc.sharepoint.com/sites/CSG_GKS_SPOsite"
$ListName="TestVersionsDocLib"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
#Get the Context
$Ctx= Get-PnPContext
#Get All Items from the List - Exclude 'Folder' List Items
$ListItems = Get-PnPListItem -List $ListName | Where {$_.FileSystemObjectType -eq "File"}
ForEach ($Item in $ListItems)
{
    #Get File Versions
    $File = $Item.File
    $Versions= $File.Versions
    $Ctx.Load($File)
    $Ctx.Load($Versions)
    $Ctx.ExecuteQuery()
    Write-host  "Scanning File:"$File.Name
     
    If($Versions.Count -gt0)
    {
        #Delete all versions
        $Versions.DeleteAll()
        $Ctx.ExecuteQuery()
        Write-Host  "Deleted All Previous Versions of the File:"$File.Name
    }
}

PS_Script_ to delete file versions on Specific Folder in doc library Tested
$SiteURL = "https://{tenant}.sharepoint.com/teams/gks/SE"
$FolderSiteRelativeUrl = "Shared Documents/04. Projects - WIP/FY'24/FY'23 - Competitor Information"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
# Test folder access
$Folder = Get-PnPFolder -Url $FolderSiteRelativeUrl
Write-Host "Folder found: $($Folder.Name)"
 
# Get files
$Files = Get-PnPFolderItem -FolderSiteRelativeUrl $FolderSiteRelativeUrl -ItemType File -Recursive
Write-Host "Found $($Files.Count) files in the folder"
 
 
foreach ($File in $Files) {
    $FileRef = $File.ServerRelativeUrl
    $FileItem = Get-PnPFile -Url $FileRef -AsListItem
    $Ctx = Get-PnPContext
    $SPFile = $FileItem.File
    $Versions = $SPFile.Versions
 
    $Ctx.Load($SPFile)
    $Ctx.Load($Versions)
    $Ctx.ExecuteQuery()
 
    Write-Host "Scanning File: $($SPFile.Name) with $($Versions.Count) versions"
 
    if ($Versions.Count -gt 2) {
        # Sort by Created date descending and skip the latest 3
        $VersionsToDelete = $Versions | Sort-Object -Property Created -Descending | Select-Object -Skip 2
 
        foreach ($Version in $VersionsToDelete) {
            $Version.DeleteObject()
        }
 
        $Ctx.ExecuteQuery()
        Write-Host "Deleted $($VersionsToDelete.Count) older versions of the file: $($SPFile.Name)"
    }
}

๐Ÿ“ Final Thoughts

Keeping version history under control is a best practice for maintaining a clean and efficient SharePoint environment. Automating this process with PowerShell ensures consistency and saves valuable administrator time.

If you need to scale this to multiple folders or automate it on a schedule, consider integrating it into an Azure Automation Runbook or a task scheduler.

Python Flask project into a distributable .exe file using PyInstaller

๐Ÿงฉ How to Convert a Complete Flask Project into a Distributable .exe File Using PyInstaller

Flask is a popular Python web framework, but distributing a Flask app to non-technical users or across an organization can be a challenge—especially when you want to avoid complex setups or Python dependencies. Fortunately, PyInstaller lets you turn your Flask app into a single Windows .exe file that can be executed without installing Python or managing virtual environments.

๐Ÿš€ To convert a complete Flask project into a distributable .exe file using PyInstaller, follow the steps outlined below:

✅ 1. Prepare Your Flask Project

Make sure your Flask app has an entry point, typically something like app.py or run.py with a structure like:

from flask import Flask

app = Flask(__name__)

@app.route('/')

def home():

    return "Hello, Flask!"

if __name__ == "__main__":

    app.run()

✅ 2. Install PyInstaller

pip install pyinstaller

✅ 3. Use the PyInstaller Command

Run the following command in your project directory to generate a standalone executable that includes all dependencies:

pyinstaller --onefile --windowed --add-data "templates;templates" --add-data "static;static" app.py

Replace app.py with the main entry-point file of your Flask app.

๐Ÿ” Explanation of Flags

--onefile: Package everything into a single .exe file.

--windowed: Hides the console window (optional; good for GUI apps).

--add-data: Includes folders like templates and static.

Format: "source;destination" (use : instead of ; on macOS/Linux).

✅ 4. Output Location

After running the command, your .exe will be in the dist/ folder:

dist/

└── app.exe

✅ 5. Distribute Within the Company

You can share the dist/run.exe file directly. Ensure:

Target machines have the required firewall permissions (Flask runs a local server).

If the app uses a browser interface, it should auto-open or include instructions to visit http://127.0.0.1:5000.

๐Ÿ’ก Optional: Auto-Open Browser on Run

In your app.py, add:

import webbrowser from threading import Timer

def open_browser():

    webbrowser.open("http://127.0.0.1:5000")

if __name__ == "__main__":

    Timer(1, open_browser).start()

    app.run()

๐ŸŽฏ Conclusion

Packaging your Flask application into a .exe file using PyInstaller is a powerful way to simplify deployment and make your app more accessible to end users. With just a few commands and a clear structure, you can create and share production-ready desktop versions of your web applications within your company—no Python installation required.


Tuesday, May 6, 2025

PowerShell script to delete file versions from the specified SharePoint document library

Managing file versions in SharePoint Online is essential to maintain storage hygiene and performance, especially when versioning is enabled for document libraries. Over time, older versions of files can accumulate and consume significant storage space. This PowerShell script demonstrates how to connect to a SharePoint Online site and delete all previous versions of files from a specified document library.

  Prerequisites

  • PnP PowerShell Module installed (Install-Module -Name "PnP.PowerShell")
  • Permissions to access the SharePoint Online site and document library
  • SharePoint versioning must be enabled for the document library

๐Ÿ“ Script Overview

$SiteURL = "https://{domain}.sharepoint.com/sites/GKS_Demosite"
$ListName="TestVersionsDocLib"
Connect-PnPOnline -Url $SiteURL -UseWebLogin
#Get the Context
$Ctx= Get-PnPContext
#Get All Items from the List - Exclude 'Folder' List Items
$ListItems = Get-PnPListItem -List $ListName | Where {$_.FileSystemObjectType -eq "File"}
ForEach ($Item in $ListItems)
{
    #Get File Versions
    $File = $Item.File
    $Versions= $File.Versions
    $Ctx.Load($File)
    $Ctx.Load($Versions)
    $Ctx.ExecuteQuery()
    Write-host  "Scanning File:"$File.Name
     
    If($Versions.Count -gt0)
    {
        #Delete all versions
        $Versions.DeleteAll()
        $Ctx.ExecuteQuery()
        Write-Host  "Deleted All Previous Versions of the File:"$File.Name
    }
}

๐Ÿ“ What the Script Does

  1. Connects to the specified SharePoint Online site using PnP PowerShell.
  2. Retrieves all file items from the specified document library (excluding folders).
  3. Loads each file’s version history.
  4. Deletes all previous versions for each file, keeping only the latest one.

⚠️ Important Notes

  • This script permanently deletes all previous versions. Make sure this is what you intend before running it.
  • Test the script in a non-production environment first.
  • You may want to add logging or backups depending on your organization’s governance policies.

๐Ÿง  Use Cases

  • Reclaiming storage space in libraries with heavy versioning.
  • Maintaining SharePoint Online quota limits.
  • Cleaning up outdated versions during migration or audits.

How to Deploy Your HTML Website on a Linux Server (Apache + SFTP)

Launching a simple HTML website on your own Linux server is easier than you might think. Whether you're sharing a static landing page or...