PowerShell: Copy SMB Share Permissions from Legacy Sources

Scenario:
Windows 2008 File Servers migration lacks a built-in function to clone Share Permissions – Get-SmbShareAccess only works on Windoze 2012. One must distinguish between NTFS ACLs and SMB Permissions. The former can be handled by Robocopy, RichCopy, Emcopy, etc. Hence, it’s necessary to use this function, by yours untruly, to get past this roadblock.

Note:
Me haz written this thing at midnight, so don’t expect the code to look pretty. I’ve tested it, and “it works on my machine.” Caveat emptor!

# Copy-SMB-Share-Permissions.ps1

# Provide SMB Share variables
$sourceSmbPath="\\LEGACYSHERVER004\SHARE009"
$destinationSmbPath="\\NUEVOSHERVER004\SHARE009"
function copySmbPermissions{
param(
[string]$sourceSmbSharePath,
[string]$destinationSmbSharePath
)

# Generate variables
$sourceSmbServerName = $sourceSmbSharePath.split("\")[2]
$sourceSmbShareName = $sourceSmbSharePath.split("\")[3]
$destinationSmbServerName = $destinationSmbSharePath.split("\")[2]
$destinationSmbShareName = $destinationSmbSharePath.split("\")[3]

# Legacy method to obtain SMB share permissions that would work with PowerShell 2.0 (Windows 2008)
function getSmbPermmissions{
param(
[string]$smbServerName,
[string]$smbPath
)
$smbObject = Get-WmiObject win32_LogicalShareSecuritySetting -ComputerName $smbServerName|?{$_.Name -eq $smbPath}
if($smbObject){
Write-Host "Collecting share permissions for $smbPath...";
$smbPermissions = @()
$acls = $smbObject.GetSecurityDescriptor().Descriptor.DACL
foreach($acl in $acls){
$user = $acl.Trustee.Name
if(!($user)){$user = $acl.Trustee.SID}
$domain = $acl.Trustee.Domain
switch($acl.AccessMask){
2032127 {$accessRight = "Full"}
1245631 {$accessRight = "Change"}
1179817 {$accessRight = "Read"}
}
$smbPermissions+=[PSCustomObject]@{IdentityReference="$domain\$user";AccessRight=$accessRight};
}
return $smbPermissions;
}
}

# This function requires PowerShell 4.0 and higher
function setSmbPermissions{
param(
$smbName=$destinationSmbShareName,
$fileServerRole=$destinationSmbServerName,
$smbPermissions=$sourceSmbPermissions
)
# Locate the host for the file server role
$roleOwner=(Get-ClusterResource -Name $fileServerRole).OwnerNode.Name
# Connect to host
$session=New-PSSession -ComputerName $roleOwner
if($session){
Write-Host "Setting share permissions for $destinationSmbShareName currently owned by $roleOwner...";
Invoke-Command -Session $session -ScriptBlock{
param($smbName,$fileServerRole,$smbPermissions)
$success=$false;
foreach ($permission in $smbPermissions){
try{
$identityReference=$permission.IdentityReference;
$accessRight=$permission.AccessRight;
$expression="Grant-SmbShareAccess -Name $smbName -ScopeName $fileServerRole -AccountName '$identityReference' -AccessRight $accessRight -Force";
write-host $expression;
Invoke-Expression $expression|Out-Null;
}
catch{
write-host $Error;
}
}
} -Args $smbName,$fileServerRole,$smbPermissions
}
Remove-PSSession $session
}

$sourceSmbPermissions=getSmbPermmissions -smbServerName $sourceSmbServerName -smbPath $sourceSmbSharePath
setSmbPermissions -smbName $destinationSmbShareName -fileServerRole $destinationSmbServerName -smbPermissions $sourceSmbPermissions
write-host "Done."
}
copySmbPermissions -sourceSmbSharePath $sourceSmbPath -destinationSmbSharePath $destinationSmbPath

Sample Output:

copySmbPermissions -sourceSmbSharePath \\LEGACYSHERVER004\SHARE009 -destinationSmbSharePath \\NUEVOSHERVER004\SHARE009
Collecting share permissions for \\LEGACYSHERVER004\SHARE009...
Setting permissions for \\NUEVOSHERVER004\SHARE009...
Done.

Update 1/9/2020 9:49PM
After I’ve had some time to fine tune this script, here’s a better version:

# Copy-SMB-Share-Permissions.ps1

# Set some SMB Share variables
$sourceSmbPath="\\OLDSHERVER\999"
$destinationSmbPath="\\NEWSHERVER\999"
$dateStamp = Get-Date -Format "yyyy-MM-dd-hhmmss"
$logFile="C:\copySmbPermissions-Log-$dateStamp.txt"

function copySmbPermissions{
param(
[string]$sourceSmbSharePath,
[string]$destinationSmbSharePath
)

# Set some variables
$sourceSmbServerName = $sourceSmbSharePath.split("\")[2];
$sourceSmbShareName = $sourceSmbSharePath.split("\")[3];
$destinationSmbServerName = $destinationSmbSharePath.split("\")[2];
$destinationSmbShareName = $destinationSmbSharePath.split("\")[3];
$GLOBAL:messages="";

# Legacy method to obtain SMB share permissions that would work with PowerShell 2.0 (Windows 2008)
function getSmbPermmissions{
param(
[string]$smbServerName,
[string]$smbPath
)
$smbShareName = $smbPath.split("\")[3]
$smbList = Get-WmiObject win32_LogicalShareSecuritySetting -ComputerName $smbServerName|?{$_.Name -notlike "*$"}
if($smbList){
$sample=$smbList.Name[0];
if($sample -match "^\\\\(.*)"){
# This accounts for Clustered File Server Role
$smbObject=$smbList|?{$_.Name -eq $smbPath};
}else{
# This accounts for Standalone File Server Role
$smbObject=$smbList|?{$_.Name -eq $smbShareName};
}
}else{
$message="Failed to retrieve SMB Permissions from $smbServerName";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -ForegroundColor Yellow;
$smbObject=$false;
}

if($smbObject){
$message="Collecting share permissions for $smbPath...";
$GLOBAL:messages+=$message+"`r`n";
Write-Host $message;
$smbPermissions = @()
$acls = $smbObject.GetSecurityDescriptor().Descriptor.DACL
foreach($acl in $acls){
$user = $acl.Trustee.Name
if(!($user)){$user = $acl.Trustee.SID}
$domain = $acl.Trustee.Domain
switch($acl.AccessMask){
2032127 {$accessRight = "Full"}
1245631 {$accessRight = "Change"}
1179817 {$accessRight = "Read"}
}
$smbPermissions+=[PSCustomObject]@{IdentityReference="$domain\$user";AccessRight=$accessRight};
}
return $smbPermissions;
}else{
return $false;
}
}

# This function requires PowerShell 4.0 and higher
function setSmbPermissions{
param(
$smbName=$destinationSmbShareName,
$fileServerRole=$destinationSmbServerName,
$smbPermissions=$sourceSmbPermissions
)
# Locate the host for the file server role
$roleOwner=(Get-ClusterResource -Name $fileServerRole -ErrorAction SilentlyContinue).OwnerNode.Name

if ($roleOwner){
# Connect to host
$message="Connecting to remote computer $roleOwner...";
$GLOBAL:messages+=$message;
write-host $message;
$maxTime=10; $duration=0;
do{
$session = New-PSSession -ComputerName $roleOwner -ErrorAction SilentlyContinue;
if (!($session)){
write-host "." -NoNewline;
$duration++;
sleep -seconds 1;
}
if ($session){
$message=".. Connected in $duration seconds.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -NoNewline;
}
} until ($session.state -match "Opened" -or $duration -eq $maxTime)

if($session){
$message="Setting share permissions for $smbName of $fileServerRole, currently owned by $roleOwner...";
$GLOBAL:messages+=$message+"`r`n";
Write-Host $message;
$invokeCommandResult=Invoke-Command -Session $session -ScriptBlock{
param($smbName,$fileServerRole,$smbPermissions)

# Declare some variables;
[int]$successCount=0;
[int]$failuresCount=0;
[bool]$singleItem=$smbPermissions.Count -eq $null -or $smbPermissions.Count -eq 1;
[string]$sessionMessages="";

foreach ($permission in $smbPermissions){
$identityReference=$permission.IdentityReference;
# This accounts for non-domain accounts
if($identityReference -match "^\\[^\\]+$"){$identityReference=$identityReference.SubString(1,$identityReference.Length-1)}
$accessRight=$permission.AccessRight;
$expression="Grant-SmbShareAccess -Name $smbName -ScopeName $fileServerRole -AccountName '$identityReference' -AccessRight $accessRight -Force";

# Using self executing anonymous function to exit try-loop upon failures
.{
try{
$success=Invoke-Expression $expression;
if(!($success)) {
$failuresCount++;
$success=$false;
return; # Exit try-loop
}
$successCount++;
$success=$true;
}
catch{
write-host $Error;
}
}
if($success){
$message="$expression => Success";
$sessionMessages+=$message+"`r`n";
write-host $message;
}else{
$message="$expression => Failure"
$sessionMessages+=$message+"`r`n";
write-host $message -ForegroundColor Yellow;
}
}

if ($singleItem){
$message="$successCount of 1 permission set is successful.";
$sessionMessages+=$message+"`r`n";
write-host $message;
}else{
$message="$successCount of $($smbPermissions.Count) permission settings are successful.";
$sessionMessages+=$message+"`r`n";
write-host $message;
}
$overallResult=$failuresCount -eq 0;
$outputArray=@($overallResult,$sessionMessages)
return $outputArray;
} -Args $smbName,$fileServerRole,$smbPermissions
Remove-PSSession $session;

$GLOBAL:messages+=$invokeCommandResult[1]+"`r`n";
$result=$invokeCommandResult[0];
}else{
$message="Unable to connect to $roleOwner.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -ForegroundColor Red;
$result=$false;
}
}else{
$message="Unable to get owner node for $fileServerRole.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -ForegroundColor Red;
$result=$false;
}
return $result;
}

$sourceSmbPermissions=getSmbPermmissions -smbServerName $sourceSmbServerName -smbPath $sourceSmbSharePath
if ($sourceSmbPermissions){
$success=setSmbPermissions -smbName $destinationSmbShareName -fileServerRole $destinationSmbServerName -smbPermissions $sourceSmbPermissions
if ($success){
$message="Permissions from $sourceSmbSharePath have been successfully copied to $destinationSmbSharePath.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message;
return $true;
}else{
$message="Unable to *SET* SMB Permissions to destination $destinationSmbSharePath.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -ForegroundColor Red;
return $false;
}
}else{
$message="Unable to *GET* SMB Permissions from $sourceSmbSharePath.";
$GLOBAL:messages+=$message+"`r`n";
write-host $message -ForegroundColor Yellow;
return $false;
}
}

copySmbPermissions -sourceSmbSharePath $sourceSmbPath -destinationSmbSharePath $destinationSmbPath
Add-Content $logFile $messages;

Leave a Reply

Your email address will not be published. Required fields are marked *