Wednesday, September 27, 2017

Ignite Session: THR3026, THE Script!!!

Without any fuzz, here it is The Script. Short and sweet just like me. Will make a better blogpost with more info so watch this blog ;-)


---Test-OneDrivePath.ps1 Script Start---


#region Header
<#
.SYNOPSIS
Check if files and folders in a OneDrive library folder is possible to syncronize

.DESCRIPTION
This script can be used to check for inconsitensies in any folder, not just OneDrive library folders.
For a list of rules that files and folders must be compliant with look at:
https://support.microsoft.com/en-au/help/3125202/restrictions-and-limitations-when-you-sync-files-and-folders

.NOTES
Author: Johansen, Reidar (reidar.johansen@lumagate.com)

History:
Date Version Author Category (NEW | CHANGE | DELETE | BUGFIX) Description
2017.09.13  1.20170913.1  Reidar  NEW  First release
2017.09.21  1.20170921.1  Reidar  BUGFIX  Improved error handling on Get-ChildItem etc.
2017.09.21  1.20170921.2  Reidar  CHANGE  Path variable no longer mandatory, defaults to current path
2017.09.21  1.20170921.3  Reidar  CHANGE  Added Warning conditions
2017.09.26  IgniteVersion Olav    CHANGE  Filetypes updated and modified for Ignite give away

.EXAMPLE
PS C:\>.\Test-OneDrivePath.ps1 -Path 'H:\HomeDirs'

This example output the result to a grid view.
.EXAMPLE
PS C:\>.\Test-OneDrivePath.ps1 -Path H:\HomeDirs' -OutFile '.\notcompliant.txt'

This example output the result to a textfile.
.PARAMETER Path
Path to the OneDrive library folder.
.PARAMETER InvalidFilenameCharacters
Characters not valid in file name. Must be a regular expression.
.PARAMETER InvalidFilenames
File names not valid. Must be a regular expression.
.PARAMETER InvalidFoldernames
Folder names not valid. Must be a regular expression.
.PARAMETER InvalidRootFoldernames
Root folder names not valid. Must be a regular expression.
.PARAMETER NumberOfFilesLimit
Maximum number of files that should exist in folder and it's sub folders.
.PARAMETER FilepathLengthLimit
Maximum number of characters for a file path, including the filename, but excluding the base path where search start from.
.PARAMETER FileSizeLimitGB
Maximum size for a file in GB.
.PARAMETER WarningFileNames
Warning if file name matches this regular expression.
.PARAMETER WarningFileSizeLimitMB
Warning size for a file in MB.
.PARAMETER OutFile
Full path and filename where the result will be stored. If not specified, result will be in a Grid View.
.PARAMETER OutGridView
If not specified, result will be returned as object.
#>
#endregion Header
#region Parameter
[CmdletBinding()]
param
(
  [Parameter(Mandatory=$false,ValueFromPipeline=$True,Position=0)]
  [string]$Path,
  [string]$InvalidFilenameCharacters='<|>|:|"|\||\?|\*|/|\\',
  [string]$InvalidFilenames='^Icon$|^.lock$|^CON$|^PRN$|^AUX$|^NUL$|.COM1$|.COM2$|.COM3$|.COM4$|.COM5$|.COM6$|.COM7$|.COM8$|.COM9$|^COM1$|^COM2$|^COM3$|^COM4$|^COM5$|^COM6$|^COM7$|^COM8$|^COM9$|.LPT1$|.LPT2$|.LPT3$|.LPT4$|.LPT5$|.LPT6$|.LPT7$|.LPT8$|.LPT9$|^LPT1$|^LPT2$|^LPT3$|^LPT4$|^LPT5$|^LPT6$|^LPT7$|^LPT8$|^LPT9$|^_vti_$',
  [string]$InvalidFoldernames='^_t$|^_w$|^_vti_$',
  [string]$InvalidRootFoldernames='^forms$',
  [int]$NumberOfFilesLimit=100000,
  [int]$FilepathLengthLimit=400,
  [int]$FileSizeLimitGB=15,
  [string]$WarningFileNames='.exe$|.hlp$|.hta$|.inf$|.ins$|.isp$|.its$|.js$|.jse$|.key$|.mht$|.msc$|.msh$|.msi$|.msp$|.mst$|.nch$|.ops$|.pif$|.prf$|.prg$|.pst$|.reg$|.scf$|.scr$|.shb$|.shs$|.url$|.vb$|.vbe$|.vbs$|.wmf$|.ws$|.wsc$|.wsf$|.wsh$',
  [int]$WarningFileSizeLimitMB=200,
  [string]$OutFile,
  [switch]$OutGridView
  )
#endregion Parameter
Set-StrictMode -Version Latest;
#region Variables
#----------------------------------
# Variables - can change if needed
#----------------------------------
[string]$scriptVersion='IgniteVersion'
#----------------------------------
# Variables - do not change
#----------------------------------
$errorlist=@();
if(-not($Path)){$Path=(Resolve-Path .\).Path;};
$fileNumber=$folderNumber=$percent2Complete=0;
$foldersinpath=@($($Path.Split('\'))|Where-Object{$_ -ne ''}).Count;
#endregion Variables
#region Functions
function Get-OutMessage
{
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory=$false,Position=0)]
    $Name,
    [Parameter(Mandatory=$false,Position=1)]
    $FullName,
    [Parameter(Mandatory=$false,Position=2)]
    $Message,
    [Parameter(Mandatory=$false,Position=3)]
    [ValidateSet('Error','Warning')]
    $Status='Error',
    [Parameter(Mandatory=$false,Position=4)]
    [bool]$IsFolder=$false
  )
  New-Object PSObject -Property @{Status=$Status;Name=$Name;IsFolder=$IsFolder;FullName=$FullName;Message=$Message};
};
function Get-OutMessageFromError
{
  [CmdletBinding()]
  param
  (
    [Parameter(Mandatory=$false,Position=0)]
    $ErrorList,
    [Parameter(Mandatory=$true,Position=1)]
    [ValidateNotNullOrEmpty()]
    $Name,
    [Parameter(Mandatory=$false,Position=2)]
    [ValidateSet('Error','Warning')]
    $Status='Error',
    [Parameter(Mandatory=$false,Position=3)]
    [switch]$IsFolder
  )
  foreach($e in $ErrorList)
  {
    # Get error message
    $msg=$e.Exception.Message;
    $fullname='';
    # Replace known errors with better message
    if($msg -match 'Could not find a part of the path')
    {
      $fullname=$msg.Replace('Could not find a part of the path','').Trim('.').Trim().Trim("'");
      $msg='Path is too long';
    };
    Get-OutMessage -Status $Status -Name $Name -IsFolder $IsFolder -FullName $fullname -Message $msg;
  };  
};
#endregion Functions
#region Main
# Check that path is accessible
if(-not(Test-Path -Path $Path -PathType Container)){throw "Path is not valid: $Path";};
# Get files in path
$files=@(Get-ChildItem -Path $Path -Recurse -File -ErrorAction SilentlyContinue -ErrorVariable getfileerrors);
# Check for errors during Get-ChildItem
$errorlist+=Get-OutMessageFromError -ErrorList $getfileerrors -Name 'Error on reading files';
$numberoffiles=if($files -is [array]){$files.Count;}else{0;};
# Number of files should not exceed limit
if($numberoffiles -gt $NumberOfFilesLimit){$errorlist+=Get-OutMessage -Name $Path -FullName $Path -Message "Total number of files is $numberoffiles, this exceeds the recommended limit of $NumberOfFilesLimit files" -IsFolder $true;};
# Get folders in path
$folders=@(Get-ChildItem -Path $Path -Recurse -Directory -ErrorAction SilentlyContinue -ErrorVariable getfoldererrors);
# Check for errors during Get-ChildItem
$errorlist+=Get-OutMessageFromError -ErrorList $getfoldererrors -Name 'Error on reading folders' -IsFolder;
$numberoffolders=if($folders -is [array]){$folders.Count;}else{0;};
# Check files in Path
foreach($file in $files)
{
  $fileNumber++;
  # Output progress
  Write-Progress -Activity "$($file.FullName)" -CurrentOperation "File number $fileNumber of $numberoffiles" -Id 1 -PercentComplete $percent2Complete -Status ("Checking files - $($percent2Complete)%");
  # File name must not contain invalid characters
  if($file.Name -match $InvalidFilenameCharacters){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message 'Invalid Character in file name';};
  # File name must not be invalid
  if($file.Name -match $InvalidFilenames){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message 'Invalid file name';};
  # Check if file name should be a warning
  if($file.Name -match $WarningFileNames){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message 'This file may be unwanted for security reasons' -Status Warning;};
  # OneNote notebooks should not be synced
  if($file.Name -match '.one$'){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message 'OneNote notebooks should not be synced';};
  # Size of a file should not exceed size limit
  if($file.Length -gt ($FileSizeLimitGB * 1024 * 1024 * 1024)){$size=[math]::Round(($file.Length/1gb),2);$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message "File size is $size GB. This exceed the limit of $FileSizeLimitGB GB";};
  # Size of a file should not exceed warning size limit
  if($file.Length -gt ($WarningFileSizeLimitMB * 1024 * 1024)){$size=[math]::Round(($file.Length/1mb),2);$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message "File size is $size MB. This exceed the limit of $WarningFileSizeLimitMB MB" -Status Warning;};
  # File name paths should not be over 400 characters, we exclude root path
  $checkbasepath=$file.FullName.Replace($Path,'').Trim('\');
  if($checkbasepath.Length -gt $FilepathLengthLimit){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message "File name path has $($checkbasepath.Length) characters. This is above the recommended limit of $FilepathLengthLimit characters";};
  # Open files can't be synced
  $test=$null;
  $locked=try{$test=[System.IO.File]::Open($file.FullName,'Open','ReadWrite','None');}catch{$true;}finally{if($test){$test.Close();$test.Dispose();$false;};};
  if($locked){$errorlist+=Get-OutMessage -Name $file.Name -FullName $file.FullName -Message 'File is locked and cannot be synced before it is closed';};
  # Calculate percentage completed for loop
  [int]$percent2Complete=if($numberoffiles){($fileNumber/$numberoffiles*100);}else{100;};
};
# Check folders in Path
$percent2Complete=0;
foreach($folder in $folders)
{
  $folderNumber++;
  # Output progress
  Write-Progress -Activity "$($folder.FullName)" -CurrentOperation "Folder number $folderNumber of $numberoffolders" -Id 1 -PercentComplete $percent2Complete -Status ("Checking folders - $($percent2Complete)%");
  # Folder name must not be invalid
  if($folder.Name -match $InvalidFoldernames){$errorlist+=Get-OutMessage -Name $folder.Name -FullName $folder.FullName -IsFolder $true -Message 'Invalid folder name';};
  # Root folder name must not invalid
  $isrootfolder=if($($(@($folder.FullName.Split('\')).Count)-1) -le $foldersinpath){$true;}else{$false;};
  if($isrootfolder -and $folder.Name -match $InvalidRootFoldernames){$errorlist+=Get-OutMessage -Name $folder.Name -FullName $folder.FullName -IsFolder $true -Message 'Invalid root folder name';};
  # Folder name paths should not be over 400 characters, we exclude root path
  $checkbasepath=$folder.FullName.Replace($Path,'').Trim('\');
  if($checkbasepath.Length -gt $FilepathLengthLimit){$errorlist+=Get-OutMessage -Name $folder.Name -FullName $folder.FullName -IsFolder $true -Message "Path has $($checkbasepath.Length) characters. This is above the recommended limit of $FilepathLengthLimit characters";};
  # Calculate percentage completed for loop
  [int]$percent2Complete=if($numberoffolders){($folderNumber/$numberoffolders*100);}else{100;};
};
# Output result
if(-not($errorlist))
{
  'No issues found!'
}
elseif($OutFile)
{
  # Output to file
  $errorlist|Select-Object Status,Message,Name,FullName|Sort-Object Status,FullName|Format-Table -AutoSize|Out-String -Width 4096|Out-File -FilePath $OutFile -Force;
}
elseif($OutGridView)
{
  # Output to Grid View
  $errorlist|Select-Object Status,Message,Name,FullName|Sort-Object Status,FullName|Out-GridView -Title 'Files and folders that needs attention';
}
else
{
  $errorlist|Select-Object Status,Message,Name,FullName|Sort-Object Status,FullName;
};
#endregion Main



---Test-OneDrivePath.ps1 Script Stop---

Migrating users homefolders or fileshares to OneDrive/Sharepoint with PowerShell

Moving files from on premise fileservers are a task more and more organizations are working with, both home folders to OneDrive and filesha...