Result
Once deployed, each endpoint will:
-
Scan its local storage for your defined file patterns
-
Generate CSV and JSON reports
-
Upload them to SharePoint using Graph API
You’ll now have a centralized dashboard of file inventories, directly accessible through your SharePoint library.
Conclusion
Automating file discovery and report uploads saves hours of manual effort and eliminates human error. By combining PowerShell, Intune, and Microsoft Graph, we’ve turned what used to be a tedious IT task into a fully autonomous workflow.
This approach scales across your entire organization and provides real-time visibility into your environment. Whether it’s for compliance, audits, or vulnerability checks. you now have a single automated solution doing the heavy lifting.
Automate File Inventory Scanning and Uploading to SharePoint using PowerShell and Intune
Table of content
As an IT admin or EUC engineer, you’ve probably been asked to identify specific files across hundreds of endpoints — whether it’s a license file, configuration file, or a particular executable. Doing that manually isn’t just inefficient it’s nearly impossible at scale.
That’s where automation comes in.
In this blog, we’ll build a PowerShell automation that does two things:
Scans your entire device for files matching a name or pattern and generates a detailed inventory report (CSV and JSON).
Automatically uploads the results to SharePoint using the Microsoft Graph API, making the reports accessible and centralized.
You can deploy this script using Microsoft Intune, allowing every managed device to report its findings back to your SharePoint site automatically.
Section 1: File Inventory Scanning with PowerShell
This section focuses on automating the discovery process — finding specific files (e.g., setup.exe, license.txt, config.json) across all drives and optionally in OneDrive folders.
Here’s what the script does:
PowerShell file inventory scanner for Windows devices
This Script scans all local filesystem drives for files matching a configurable search string or list of patterns. The result is stored in CSV and JSON at C:\ProgramData\FileInventory location.
<# .SYNOPSIS Client-side PowerShell file-inventory scanner for Windows devices. .DESCRIPTION - Scans all local filesystem drives for files matching a configurable search string or list of patterns. - Optionally includes OneDrive for Business (per-user) locations. Can be disabled. - Outputs results to CSV and JSON, and writes a short log to the console. - Supports configuration via a JSON file (C:\ProgramData\FileScanConfig.json) so you can update search criteria without changing the script binary deployed to clients. .NOTES Author: Skatufa Usage: Deploy via Intune (PowerShell script). When running in System context. Run using : powershell -ep bypass -file "C:\Users\Atoofa\Downloads\testnew.ps1" "*google*" #> param( [string]$SearchString = '', [string[]]$SearchPatterns = @(), [switch]$DisableOneDrive = $false, [string]$OutputFolder = "$env:ProgramData\FileInventory", [switch]$IncludeFileVersionInfo = $true, [switch]$VerboseLogging = $false ) # Helper: Load config from C:\ProgramData\FileScanConfig.json (if exists) $ConfigPath = 'C:\ProgramData\FileScanConfig.json' if (Test-Path $ConfigPath) { try { $cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json -ErrorAction Stop if ($null -ne $cfg.SearchString -and -not [string]::IsNullOrWhiteSpace($cfg.SearchString)) { $SearchString = $cfg.SearchString } if ($null -ne $cfg.SearchPatterns -and $cfg.SearchPatterns.Count -gt 0) { $SearchPatterns = $cfg.SearchPatterns } if ($cfg.DisableOneDrive -eq $true) { $DisableOneDrive = $true } if ($null -ne $cfg.OutputFolder -and -not [string]::IsNullOrWhiteSpace($cfg.OutputFolder)) { $OutputFolder = $cfg.OutputFolder } } catch { Write-Verbose "Failed to load config $ConfigPath : $_" } } # If user provided only a SearchString, build wildcard pattern if (($SearchPatterns -eq $null) -or ($SearchPatterns.Count -eq 0)) { if (-not [string]::IsNullOrWhiteSpace($SearchString)) { $SearchPatterns = @("*$SearchString*") } else { Write-Host "No search string/pattern provided. Provide -SearchString or deploy a config file to $ConfigPath." -ForegroundColor Yellow exit 2 } } # Ensure output folder exists try { New-Item -Path $OutputFolder -ItemType Directory -Force | Out-Null } catch { } $Hostname = $env:COMPUTERNAME $Now = Get-Date -Format 'yyyyMMdd_HHmmss' $CsvPath = Join-Path $OutputFolder "FileInventory_${Hostname}_$Now.csv" $JsonPath = Join-Path $OutputFolder "FileInventory_${Hostname}_$Now.json" # Build list of root paths to search (all file-system PSDrives) $fsDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -and (Test-Path $_.Root) } $rootPaths = @() foreach ($d in $fsDrives) { $rootPaths += $d.Root } # Add OneDrive folders if not disabled and running in user context $includeOneDrive = -not $DisableOneDrive if ($includeOneDrive) { if ($env:USERNAME -and $env:USERNAME -ne 'SYSTEM') { $oneDriveCandidates = @( "$env:USERPROFILE\OneDrive", "$env:USERPROFILE\OneDrive - *", "$env:USERPROFILE\OneDrive - * - Personal" ) foreach ($p in $oneDriveCandidates) { try { $resolved = Get-ChildItem -Path $p -Directory -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } if ($resolved) { $rootPaths += $resolved } } catch { } } } else { Write-Verbose "OneDrive scan requested but running as SYSTEM or cannot access profile. Skipping OneDrive paths." -Verbose:$VerboseLogging } } # Deduplicate $rootPaths = $rootPaths | Sort-Object -Unique Write-Host "Scanning $($rootPaths.Count) root path(s) for patterns: $($SearchPatterns -join ', ')" -ForegroundColor Cyan $results = [System.Collections.Generic.List[PSObject]]::new() # Function to build result object (compatible with PS 5.1) function New-ResultObject { param($fileInfo, $fileVersion) $owner = $null if ($null -ne $fileInfo.FullName) { try { $owner = (Get-Acl -LiteralPath $fileInfo.FullName -ErrorAction Stop).Owner } catch { $owner = $null } } [PSCustomObject]@{ Hostname = $Hostname FileName = $fileInfo.Name FullName = $fileInfo.FullName FilePath = $fileInfo.DirectoryName SizeBytes = $fileInfo.Length DateCreated = $fileInfo.CreationTimeUtc.ToString('o') DateAccessed = $fileInfo.LastAccessTimeUtc.ToString('o') DateModified = $fileInfo.LastWriteTimeUtc.ToString('o') Owner = $owner ProductName = $fileVersion.ProductName ProductVersion = $fileVersion.ProductVersion FileDescription = $fileVersion.FileDescription ScanTimestamp = (Get-Date).ToString('o') } } # Start scanning foreach ($root in $rootPaths) { foreach ($pattern in $SearchPatterns) { Write-Verbose "Searching root: $root pattern: $pattern" -Verbose:$VerboseLogging try { $files = Get-ChildItem -LiteralPath $root -Filter $pattern -File -Recurse -ErrorAction SilentlyContinue } catch { try { $files = Get-ChildItem -LiteralPath $root -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like $pattern } } catch { Write-Verbose "Failed to enumerate $root : $_" -Verbose:$VerboseLogging continue } } foreach ($f in $files) { $fv = [PSCustomObject]@{ ProductName = $null; ProductVersion = $null; FileDescription = $null } if ($IncludeFileVersionInfo) { try { $vi = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($f.FullName) $fv.ProductName = $vi.ProductName $fv.ProductVersion = $vi.ProductVersion $fv.FileDescription = $vi.FileDescription } catch { } } $results.Add((New-ResultObject -fileInfo $f -fileVersion $fv)) } } } # Write outputs $resultsArray = $results.ToArray() try { if ($resultsArray.Count -gt 0) { $resultsArray | Export-Csv -Path $CsvPath -NoTypeInformation -Force $resultsArray | ConvertTo-Json -Depth 5 | Out-File -FilePath $JsonPath -Encoding UTF8 -Force Write-Host "Found $($resultsArray.Count) matching file(s). CSV -> $CsvPath JSON -> $JsonPath" -ForegroundColor Green } else { Write-Host "No matching files found." -ForegroundColor Yellow } } catch { Write-Host "Failed to write output: $_" -ForegroundColor Red } # Audit log entry $AuditFile = Join-Path $OutputFolder "FileInventory_Audit.log" $summary = "$(Get-Date -Format o) - Host:$Hostname - Patterns:$($SearchPatterns -join ',') - Results:$($resultsArray.Count) - OutputCSV:$CsvPath" Add-Content -Path $AuditFile -Value $summary -ErrorAction SilentlyContinue # Show sample in console if ($resultsArray.Count -gt 0) { $resultsArray | Select-Object Hostname, FileName, FullName, ProductName, ProductVersion, FileDescription, FilePath, DateCreated, DateAccessed | Select-Object -First 20 | Format-Table -AutoSize } exit 0Once the script is ran, it will create below files.

Section 2: Uploading the Report to SharePoint Automatically
Now that your scan results are generated, let’s automate their upload to SharePoint.
We’ll use Microsoft Graph API to securely authenticate (via App Registration) and upload the generated files to a SharePoint Document Library. This ensures all your file inventory reports are centralized and accessible for auditing or analysis.
Pre-requisites
Register an app in Azure AD with permissions ( Sites.FullControl.All, Sites.ReadWrite.All,Sites.Selected )
Generate a Client Secret and note your: Tenant ID, Client ID and Client Secret. You can refer to blog for setting up API permission and App from blog How to Upload Files to SharePoint Online Using Microsoft Graph API and PowerShell – Tech EUC
Identify your SharePoint Site ID and Document Library (Drive) ID
Here is your full script which will scan the windows files based on names and then automatically upload the results to SharePoint.
<# .SYNOPSIS File inventory scanner and uploader to SharePoint. .DESCRIPTION - Scans local file system (and optionally OneDrive folders) for matching files. - Saves results to CSV/JSON. - Uploads CSV result to SharePoint Online via Microsoft Graph API. .NOTES Author: Skatufa Last Updated: 2025-10-23 #> param( [string]$SearchString = '', [string[]]$SearchPatterns = @(), [switch]$DisableOneDrive = $false, [string]$OutputFolder = "$env:ProgramData\FileInventory", [switch]$IncludeFileVersionInfo = $true, [switch]$VerboseLogging = $false ) # ─── 1️ Load Config ────────────────────────────────────────────────────────────── $ConfigPath = 'C:\ProgramData\FileScanConfig.json' if (Test-Path $ConfigPath) { try { $cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json -ErrorAction Stop if ($null -ne $cfg.SearchString -and -not [string]::IsNullOrWhiteSpace($cfg.SearchString)) { $SearchString = $cfg.SearchString } if ($null -ne $cfg.SearchPatterns -and $cfg.SearchPatterns.Count -gt 0) { $SearchPatterns = $cfg.SearchPatterns } if ($cfg.DisableOneDrive -eq $true) { $DisableOneDrive = $true } if ($null -ne $cfg.OutputFolder -and -not [string]::IsNullOrWhiteSpace($cfg.OutputFolder)) { $OutputFolder = $cfg.OutputFolder } } catch { Write-Verbose "Failed to load config $ConfigPath : $_" } } if (($SearchPatterns -eq $null) -or ($SearchPatterns.Count -eq 0)) { if (-not [string]::IsNullOrWhiteSpace($SearchString)) { $SearchPatterns = @("*$SearchString*") } else { Write-Host " No search string or pattern provided." -ForegroundColor Yellow exit 2 } } # ─── 2️ Prepare Output Paths ──────────────────────────────────────────────────── try { New-Item -Path $OutputFolder -ItemType Directory -Force | Out-Null } catch { } $Hostname = $env:COMPUTERNAME $Now = Get-Date -Format 'yyyyMMdd_HHmmss' $CsvPath = Join-Path $OutputFolder "FileInventory_${Hostname}_$Now.csv" $JsonPath = Join-Path $OutputFolder "FileInventory_${Hostname}_$Now.json" # ─── 3️ Build Search Paths ─────────────────────────────────────────────────────── $fsDrives = Get-PSDrive -PSProvider FileSystem | Where-Object { $_.Root -and (Test-Path $_.Root) } $rootPaths = $fsDrives.Root if (-not $DisableOneDrive -and $env:USERNAME -and $env:USERNAME -ne 'SYSTEM') { $oneDriveCandidates = @("$env:USERPROFILE\OneDrive", "$env:USERPROFILE\OneDrive - *", "$env:USERPROFILE\OneDrive - * - Personal") foreach ($p in $oneDriveCandidates) { $resolved = Get-ChildItem -Path $p -Directory -ErrorAction SilentlyContinue | ForEach-Object { $_.FullName } if ($resolved) { $rootPaths += $resolved } } } $rootPaths = $rootPaths | Sort-Object -Unique Write-Host " Scanning $($rootPaths.Count) root path(s) for patterns: $($SearchPatterns -join ', ')" -ForegroundColor Cyan # ─── 4️ Perform File Scan ──────────────────────────────────────────────────────── $results = [System.Collections.Generic.List[PSObject]]::new() function New-ResultObject { param($fileInfo, $fileVersion) $owner = $null try { $owner = (Get-Acl -LiteralPath $fileInfo.FullName -ErrorAction Stop).Owner } catch {} [PSCustomObject]@{ Hostname = $env:COMPUTERNAME FileName = $fileInfo.Name FullName = $fileInfo.FullName FilePath = $fileInfo.DirectoryName SizeBytes = $fileInfo.Length DateCreated = $fileInfo.CreationTimeUtc.ToString('o') DateAccessed = $fileInfo.LastAccessTimeUtc.ToString('o') DateModified = $fileInfo.LastWriteTimeUtc.ToString('o') Owner = $owner ProductName = $fileVersion.ProductName ProductVersion = $fileVersion.ProductVersion FileDescription = $fileVersion.FileDescription ScanTimestamp = (Get-Date).ToString('o') } } foreach ($root in $rootPaths) { foreach ($pattern in $SearchPatterns) { try { $files = Get-ChildItem -LiteralPath $root -Filter $pattern -File -Recurse -ErrorAction SilentlyContinue } catch { $files = Get-ChildItem -LiteralPath $root -File -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -like $pattern } } foreach ($f in $files) { $fv = [PSCustomObject]@{ ProductName = $null; ProductVersion = $null; FileDescription = $null } if ($IncludeFileVersionInfo) { try { $vi = [System.Diagnostics.FileVersionInfo]::GetVersionInfo($f.FullName) $fv.ProductName = $vi.ProductName $fv.ProductVersion = $vi.ProductVersion $fv.FileDescription = $vi.FileDescription } catch {} } $results.Add((New-ResultObject -fileInfo $f -fileVersion $fv)) } } } # ─── 5️ Write Outputs ─────────────────────────────────────────────────────────── if ($results.Count -gt 0) { $results | Export-Csv -Path $CsvPath -NoTypeInformation -Force $results | ConvertTo-Json -Depth 5 | Out-File -FilePath $JsonPath -Encoding UTF8 -Force Write-Host " Found $($results.Count) file(s). CSV saved to $CsvPath" -ForegroundColor Green } else { Write-Host " No matching files found." -ForegroundColor Yellow } # ─── 6️ Upload to SharePoint ──────────────────────────────────────────────────── Write-Host " Uploading results to SharePoint..." -ForegroundColor Cyan $Tenant = "WARYAATECH" $ClientID = "b363318e-20e3-4340-88f6-a764aac4e0c5" $Secret = "yEo8Q~edXKnNvCDNd~xx" $SharePoint_SiteID = "2662f6e0-7e88-415d-a825-a86a6837a73b" $SharePoint_Path = "https://waryaatech.sharepoint.com/sites/Waryaastorage/Shared%20DocumentS" $SharePoint_ExportFolder = "FileScanResults" $Body = @{ client_id = $ClientID client_secret = $Secret scope = "https://graph.microsoft.com/.default" grant_type = 'client_credentials' } $Graph_Url = "https://login.microsoftonline.com/$Tenant.onmicrosoft.com/oauth2/v2.0/token" $Token = Invoke-RestMethod -Uri $Graph_Url -Method Post -Body $Body $Access_token = $Token.access_token $Header = @{ Authorization = $Access_token; "Content-Type" = "application/json" } $SharePoint_Graph_URL = "https://graph.microsoft.com/v1.0/sites/$SharePoint_SiteID/drives" $Result = Invoke-RestMethod -Uri $SharePoint_Graph_URL -Method GET -Headers $Header $DriveID = $Result.value | Where-Object { $_.webURL -eq $SharePoint_Path } | Select-Object -ExpandProperty id $FileName = Split-Path $CsvPath -Leaf $UploadSessionUri = "https://graph.microsoft.com/v1.0/sites/${SharePoint_SiteID}/drives/${DriveID}/root:/${SharePoint_ExportFolder}/${FileName}:/createUploadSession" $UploadSession = Invoke-RestMethod -Uri $UploadSessionUri -Method POST -Headers $Header -ContentType "application/json" $fileBytes = [System.IO.File]::ReadAllBytes($CsvPath) $fileLength = $fileBytes.Length $headers = @{ 'Content-Range' = "bytes 0-$($fileLength-1)/$fileLength" } Invoke-RestMethod -Method Put -Uri $UploadSession.uploadUrl -Body $fileBytes -Headers $headers Write-Host " Successfully uploaded $FileName to SharePoint folder '$SharePoint_ExportFolder'" -ForegroundColor Green # ─── 7️⃣ Logging ──────────────────────────────────────────────────────────────── $AuditFile = Join-Path $OutputFolder "FileInventory_Audit.log" $summary = "$(Get-Date -Format o) - Host:$Hostname - Results:$($results.Count) - OutputCSV:$CsvPath - UploadedTo:$SharePoint_ExportFolder" Add-Content -Path $AuditFile -Value $summary -ErrorAction SilentlyContinue exit 0How to Deploy via Intune (Win32 App Method) or SCCM Package/Application
You can deploy this script from Intune as Win32 Application, Or if you need to deploy this as script, then you can specify the search string inside the script directly.
If you are using SCCM, then you can create this as Package model and the install command you can set as
powershell -ep bypass -file “ScriptName.ps1” “*google*”
This script also search multiple patterns where you can pass the arguments like below
powershell -ep bypass -file “ScriptName.ps1” “*google*”, “*zoom*”
Result
Once deployed, each endpoint will:
Scan its local storage for your defined file patterns
Generate CSV and JSON reports
Upload them to SharePoint using Graph API
You’ll now have a centralized dashboard of file inventories, directly accessible through your SharePoint library.
Conclusion
Automating file discovery and report uploads saves hours of manual effort and eliminates human error. By combining PowerShell, Intune, and Microsoft Graph, we’ve turned what used to be a tedious IT task into a fully autonomous workflow.
This approach scales across your entire organization and provides real-time visibility into your environment. Whether it’s for compliance, audits, or vulnerability checks. you now have a single automated solution doing the heavy lifting.
Table of content
Subscribe to Blog
Signup to our weekly newsletter
category
Connect with Us
Recommended Posts
Deploy Win32 Apps Using Intune – Step-by-Step .IntuneWin Packaging & Deployment Guide (2026)
Planning for Distribution Points in SCCM
What Is a Distribution Point in SCCM / MECM?