PowerShell script exchange
Post Reply
appleoddity
Novice
Posts: 9
Liked: 2 times
Joined: Mar 10, 2022 5:08 pm
Contact:

PowerShell vs GUI Behavior for NAS Backup Objects (NetApp SVM / Share Path Differences)

Post by appleoddity »

Subject: PowerShell vs GUI Behavior for NAS Backup Objects (NetApp SVM / Share Path Differences)

Hi all,

I'm running into a disconnect between what the Veeam GUI allows and what I can accomplish via PowerShell when working with NetApp NAS filers.

Environment / Context

- NetApp storage systems are added to Veeam as NAS Filers
- In the GUI, I can create a NAS backup job and add:
- - Entire NAS Filer
- - Specific SVM
- - Specific share under an SVM

The GUI represents these paths as:

Code: Select all

<netapphost>
<netapphost>\<svm>
<netapphost>\<svm>:\<share_name>
What I See in PowerShell

When querying an existing job:

Code: Select all

Get-VBRUnstructuredBackupJob
The BackupObject array correctly reflects the Path as:
- Host: <netapphost>
- SVM: <netapphost>\<svm>
- Share: <netapphost>\<svm>:\<share_name>

Creating Objects via PowerShell

To add objects, I must use:
- New-VBRNASBackupJobObject
- Then pass that into:
Add-VBRNASBackupJob

Example – Entire NAS Filer (Works as Expected)

Code: Select all

New-VBRNASBackupJobObject -SanEntity (Get-NetAppHost -Name <netapphost>)
Example – File Share (Problematic)

Code: Select all

New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -Name <netapphost>\<svm>:\<share_name>)
Issue #1 – Path Translation

The resulting object has its Path converted to:

Code: Select all

\\<IP Address>\<share_name>
This carries through into:

Code: Select all

Get-VBRUnstructuredBackupJob
However, in the GUI, it still displays as:

Code: Select all

<netapphost>\<svm>:\<share_name>
Issue #2 – Missing SVM-Level Object Creation

I cannot find any way in PowerShell to create an object representing:

Code: Select all

<netapphost>\<svm>
There does not appear to be a combination of:
- -SanEntity
- -Server + -Path

that allows adding an SVM the same way the GUI does.

Questions

1) How can I create an SVM-level backup object via PowerShell?
(Equivalent to adding <netapphost>\<svm> in the GUI)

2) How can I add a share via PowerShell so that the path remains:

Code: Select all

<netapphost>\<svm>:\<share_name>
instead of being converted to:

Code: Select all

\\<IP Address>\<share_name>
Concern

Functionally, the jobs appear to run the same. However, it's concerning that:
- The underlying job configuration differs
- The Path value is inconsistent depending on whether the object is added via GUI vs PowerShell

This makes automation feel unreliable or at least opaque.

Any insight into how to properly align PowerShell behavior with the GUI would be greatly appreciated.

Thanks!
appleoddity
Novice
Posts: 9
Liked: 2 times
Joined: Mar 10, 2022 5:08 pm
Contact:

Re: PowerShell vs GUI Behavior for NAS Backup Objects (NetApp SVM / Share Path Differences)

Post by appleoddity »

It doesn't look like I can edit my post. I wanted to be a bit more specific on what the problem here is. I believe this problem is specifically around the use of the 'New-VBRNASBackupJobObject' cmdlet.

When I configure a backup job that contains NAS Filer shares in the GUI. For example, say I add the following two SMB shares:

Code: Select all

\\netapp01-nas\share1
\\netapp01-nas\share2
Afterwards, I can query the backup objects in powershell:

Code: Select all

(Get-VBRUnstructuredBackupJob -name test-job).BackupObject

Server                                           InclusionMask ExclusionMask            Path
------                                           ------------- -------------            ----
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} netapp01\netapp01-nas\vol1\share1:SanSmb
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} netapp01\netapp01-nas\vol1\share2:SanSmb
Note that the Path contains a fully qualified reference to the NAS Filer, the SVM, and the actual Volume/Path where the share resides (/vol1/share1).

If I take a look at the Server object for one of the above items, I see something like this:

Code: Select all

(Get-VBRUnstructuredBackupJob -name test-job).BackupObject[0].Server

AccessCredentials        : veeam
ProxyMode                : SelectedProxy
SelectedProxyServers     : {fe5fe9d9-9329-4bf0-80cf-1c698c6a94cc}
EnableSnapDiff           : True
InheritSettingsFromFiler : True
Type                     : SANSMB
BackupIOControlLevel     : High
Id                       : 9e1faaf9-615e-4e58-bd01-a7cf4b91449e
CacheRepository          : Veeam.Backup.Core.CBackupRepository
Now, let's say I create a new NAS filer job from Powershell using the objects from the original job:

Code: Select all

$job = Get-VBRUnstrucuteredBackupJob -name test-job
Add-VBRNASBackupJob -BackupObject $job.BackupObject -Name test-job2 -ShortTermBackupRepository $job.ShortTermBackupRepository
(Get-VBRUnstructuredBackupJob -name test-job2).BackupObject

Server                                           InclusionMask ExclusionMask            Path
------                                           ------------- -------------            ----
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} netapp01\netapp01-nas\vol1\share1:SanSmb
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} netapp01\netapp01-nas\vol1\share2:SanSmb
Perfect. The Path is what I'm looking for. But, let's say I don't have these original objects to copy from. Following the Veeam documentation, I'm supposed to use the 'New-VBRNASBackupJobObject' cmdlet to create it.

Code: Select all

New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -id ($job.BackupObject[0].Server.Id))

Server                                           InclusionMask ExclusionMask            Path
------                                           ------------- -------------            ----
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} \\172.16.8.52\share1
No Bueno! Notice the Path has been translated into an IP address and share name instead of a fully qualified reference to the NAS Filer.

We're clearly working with the same share though:

Code: Select all

(New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -id ($job.BackupObject[0].Server.Id))).Server

AccessCredentials        : veeam
ProxyMode                : SelectedProxy
SelectedProxyServers     : {fe5fe9d9-9329-4bf0-80cf-1c698c6a94cc}
EnableSnapDiff           : True
InheritSettingsFromFiler : True
Type                     : SANSMB
BackupIOControlLevel     : High
Id                       : 9e1faaf9-615e-4e58-bd01-a7cf4b91449e
CacheRepository          : Veeam.Backup.Core.CBackupRepository
The next logical thing is to try something like this:

Code: Select all

New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -id ($job.BackupObject[0].Server.Id)) -Path netapp01\netapp01-nas\vol1\share1:SanSmb
New-VBRNASBackupJobObject : Remove Path parameter.
Parameter name: Path
At line:1 char:1
+ New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -id ($jo ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [New-VBRNASBackupJobObject], ArgumentException
    + FullyQualifiedErrorId : System.ArgumentException,Veeam.Backup.PowerShell.Cmdlets.NewVBRNASBackupJobObject
No Bueno! No combination of -Server and -Path seems acceptable. It will not let me use the two together even though it is clearly a supported combination through documentation and independent review of the command.

So, let's apply the backup object I do get to the job:

Code: Select all

(Get-VBRUnstructuredBackupJob -Name test-job2 | Set-VBRNASBackupJob -BackupObject (New-VBRNASBackupJobObject -Server (Get-VBRUnstructuredServer -id ($job.BackupObject[0].Server.Id)))).BackupObject

Server                                           InclusionMask ExclusionMask            Path
------                                           ------------- -------------            ----
Veeam.Backup.PowerShell.Infos.VBRSANNASSMBServer {}            {\.snapshot, \~snapshot} \\172.16.8.52\share1
Again, no surprise, but the translated Path flows through to the job as well.

So, the question becomes, HOW do I create the backup objects that the GUI is clearly adding?

This has a trickle down effect that if I look at the disk storage (in the GUI) after one of the backup jobs runs, it also shows the object like '\\172.16.8.52\share1' whereas all the jobs added through the GUI show the fully qualified NAS filer object.
appleoddity
Novice
Posts: 9
Liked: 2 times
Joined: Mar 10, 2022 5:08 pm
Contact:

Re: PowerShell vs GUI Behavior for NAS Backup Objects (NetApp SVM / Share Path Differences)

Post by appleoddity »

Alright after many, many hours of reverse engineering this. I finally got it. The New-VBRNASBackupJobObject command is either broken, or deficient in it's behavior. Therefore, we switch to directly calling the constructor of the VBRNASBackupJobObject type. The core problem here is that it appears the VBRNASBackupJobObject has two path properties (Path and internalPath). The Path seems to be mostly like a display name in the case of NAS filer shares, and this is what trickles down into the GUI. The underlying internalPath does reference the fully qualified NAS Filer path:

Code: Select all

netapp01\netapp01-nas\vol1\share1:SanSmb
And backups do seem to run correctly, but it is very annoying to see hardcoded UNC paths with IP addresses in the backup storage, for instance. The other issue is that New-VBRNASBackupJobObject goes through some type of resolution of the share - and if your Netapp has multiple Lifs Veeam will have "randomly" added the shares into inventory using different underlying IP addresses. I had numerous issues with New-VBRNASBackupJobObject failing out because of "Access Denied" (although that error seemed generic for something went wrong) until I disabled the extra Lifs and rescanned the storage. I believe the below solution solves for this, and makes the creation of the backup object a lot faster as well.

There is an fsItemType parameter hardcoded into this function with a value of 'SanContainer' - the available enum types are:

Code: Select all

File
Directory
SanVolume
SanContainer
SanFiler
Unknown
I think these are mostly self-explanatory, but for anyone in the future, this is probably the key to adding an SVM, or a full Netapp to the job, as asked about in the original question. I'm out of time to keep iterating through this project.

It's also important to note that this is for SMB shares. NFS shares may be a bit different.

Here is a replacement function and sample for New-VBRNASBackupJobObject:

Problem: When using New-VBRNASBackupJobObject with NetApp SAN/NAS shares, Veeam creates objects with UNC paths (\\ip\share) instead of the proper SAN path format (controller\vserver\vol\share:SanSmb) seen when using the GUI. This doesn't necessarily break things, but the persistence of this pathing trickles down in the Veeam GUI and looks messy. Similar to how we would feel if we were hardcoding IP addresses into anything programmatically.

Solution: Use reflection to call the VBRNASBackupJobObject constructor directly, which allows setting the Path property correctly.

Function:

Code: Select all

function New-VBRNASBackupJobObjectDirect {
    <#
    .SYNOPSIS
        Creates a VBRNASBackupJobObject using direct constructor invocation.

    .DESCRIPTION
        The standard New-VBRNASBackupJobObject cmdlet creates objects with UNC paths
        instead of the proper SAN/NetApp paths. This function uses reflection to call
        the constructor directly, allowing us to set the Path property correctly.

        The Path format for NetApp is: <controller>\<vserver>\<vol\path>:SanSmb
        Example: netapp01\svm01\vol1\share1:SanSmb

    .PARAMETER Server
        The VBRUnstructuredServer object (from Get-VBRUnstructuredServer)

    .PARAMETER Controller
        The NetApp controller name (e.g., netapp01)

    .PARAMETER Vserver
        The NetApp vserver/SVM name (e.g., svm01)

    .PARAMETER VolPath
        The volume and path (e.g., vol1\share1 or /vol1/share1)

    .PARAMETER InclusionMasks
        Optional array of inclusion masks

    .PARAMETER ExclusionMasks
        Optional array of exclusion masks (defaults to \.snapshot, \~snapshot)
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Server,

        [Parameter(Mandatory = $true)]
        [string]$Controller,

        [Parameter(Mandatory = $true)]
        [string]$Vserver,

        [Parameter(Mandatory = $true)]
        [string]$VolPath,

        [Parameter()]
        [object[]]$InclusionMasks,

        [Parameter()]
        [object[]]$ExclusionMasks
    )

    try {
        $type = [Veeam.Backup.PowerShell.Infos.VBRNASBackupJobObject]
        $bindingFlags = [System.Reflection.BindingFlags]::Instance -bor `
                        [System.Reflection.BindingFlags]::NonPublic -bor `
                        [System.Reflection.BindingFlags]::Public

        # Find the 6-parameter constructor - there is only one, but just in case.
        $ctor = $type.GetConstructors($bindingFlags) | Where-Object { $_.GetParameters().Count -eq 6 }
        if (-not $ctor) {
            throw "Could not find VBRNASBackupJobObject constructor"
        }

        # Get the fsItemType enum type and value
        $fsItemTypeParam = $ctor.GetParameters() | Where-Object { $_.Name -eq 'fsItemType' }
        $fsItemTypeType = $fsItemTypeParam.ParameterType
        $fsItemType = [Enum]::Parse($fsItemTypeType, 'SanContainer')

        # Normalize VolPath: convert forward slashes to backslashes and remove leading slashes
        $normalizedVolPath = ($VolPath -replace '/', '\').TrimStart('\')

        # Build the Path in NetApp format: <controller>\<vserver>\<vol\path>:SanSmb
        $path = "{0}\{1}\{2}:SanSmb" -f $Controller.ToLower(), $Vserver.ToLower(), $normalizedVolPath.ToLower()
        $internalPath = $path

        # Default exclusion masks if not provided
        if (-not $ExclusionMasks) {
            [Veeam.Backup.PowerShell.Infos.VBRNASBackupMask[]]$ExclusionMasks = @(
                New-VBRNASBackupPathMask -Path '\.snapshot'
                New-VBRNASBackupPathMask -Path '\~snapshot'
            )
        }

        if (-not $InclusionMasks) {
            [Veeam.Backup.PowerShell.Infos.VBRNASBackupMask[]]$InclusionMasks = @()
        }

        # Unwrap PSObject wrapper if present - reflection requires the raw .NET object
        $serverObj = if ($Server.psobject.BaseObject) { $Server.psobject.BaseObject } else { $Server }

        # Invoke constructor: (server, path, internalPath, inclusionMasks, exclusionMasks, fsItemType)
        $newObject = $ctor.Invoke(@($serverObj, $path, $internalPath, $InclusionMasks, $ExclusionMasks, $fsItemType))
        return $newObject
    }
    catch {
        Write-Error "Failed to create VBRNASBackupJobObject: $_"
        return $null
    }
}
Usage Example:

Code: Select all

# Get the server object using the Veeam path format (controller\vserver:\sharename)
$veeamPath = "netapp01\svm01:\myshare"
$server = Get-VBRUnstructuredServer -Name $veeamPath

# Create the backup job object with correct SAN path
$nasObject = New-VBRNASBackupJobObjectDirect `
    -Server $server `
    -Controller "netapp01" `
    -Vserver "svm01" `
    -VolPath "vol1\myshare"

# Verify the path is correct
$nasObject.Path
# Should output: netapp01\svm01\vol1\myshare:SanSmb

# Use the object to create a backup job
$repository = Get-VBRBackupRepository -Name "MyRepository"

Add-VBRNASBackupJob `
    -Name "MyNASBackup" `
    -BackupObject $nasObject `
    -ShortTermBackupRepository $repository `
    -ShortTermRetentionType Days `
    -ShortTermRetentionPeriod 14
Key Points:
  • The standard cmdlet creates UNC paths like [FONT=Courier New]\\172.16.x.x\share[/FONT].
  • The "correct" path format is [FONT=Courier New]controller\vserver\vol\path:SanSmb[/FONT]
  • The fsItemType must be set to 'SanContainer' for NetApp volumes
  • PowerShell wraps objects in PSObject - reflection requires the raw .NET object via [FONT=Courier New].psobject.BaseObject[/FONT]
  • Tested with VBR v12
appleoddity
Novice
Posts: 9
Liked: 2 times
Joined: Mar 10, 2022 5:08 pm
Contact:

Re: PowerShell vs GUI Behavior for NAS Backup Objects (NetApp SVM / Share Path Differences)

Post by appleoddity »

Veeam please listen - I should be able to edit or delete previous posts!

Correction on my previous post. After adding a backup copy job there were some failures and I had to make some fixes.

The correct format for the Path and internalPath is:

Code: Select all

<controller>\<vserver>\<volume>\<share_name>:SanSmb
It appears there might be case sensitivity here. Even if not I've decided to match what the GUI does which preserves the casing in the path except for the share name which is lowercased.

Finally, the fsItemType must be 'Unknown' - this appeared to be the primary issue and without this value the backup copy jobs always failed.

New function and examples:
Problem: When using New-VBRNASBackupJobObject with NetApp SAN/NAS shares, Veeam creates objects with UNC paths (\\ip\share) instead of the proper SAN path format (controller\vserver\volume\sharename:SanSmb). This does not match how the GUI does it and is messy with embedded IP addresses.

Solution: Use reflection to call the VBRNASBackupJobObject constructor directly, which allows setting the Path property correctly.

Important Notes:
  • Controller, Vserver, and Volume names are CASE-SENSITIVE - preserve original case
  • Only ShareName is converted to lowercase
  • The fsItemType must be set to 'Unknown' for NetApp NAS shares
  • PowerShell wraps objects in PSObject - reflection requires the raw .NET object via .psobject.BaseObject
  • Tested with Veeam B&R v12
Function:

Code: Select all

function New-VBRNASBackupJobObjectDirect {
    <#
    .SYNOPSIS
        Creates a VBRNASBackupJobObject using direct constructor invocation.

    .DESCRIPTION
        The standard New-VBRNASBackupJobObject cmdlet creates objects with UNC paths
        instead of the proper SAN/NetApp paths. This function uses reflection to call
        the constructor directly, allowing us to set the Path property correctly.

        The Path format for NetApp is: <controller>\<vserver>\<volume>\<sharename>:SanSmb
        - Controller, Vserver, and Volume are CASE-SENSITIVE (preserve original case)
        - ShareName is converted to lowercase

    .PARAMETER Server
        The VBRUnstructuredServer object (from Get-VBRUnstructuredServer)

    .PARAMETER Controller
        The NetApp controller name - CASE-SENSITIVE (e.g., netapp01)

    .PARAMETER Vserver
        The NetApp vserver/SVM name - CASE-SENSITIVE (e.g., svm01)

    .PARAMETER VolumeName
        The NetApp volume name - CASE-SENSITIVE (e.g., vol_data)

    .PARAMETER ShareName
        The share name (converted to lowercase)

    .PARAMETER InclusionMasks
        Optional array of inclusion masks

    .PARAMETER ExclusionMasks
        Optional array of exclusion masks (defaults to \.snapshot, \~snapshot)
    #>
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [object]$Server,

        [Parameter(Mandatory = $true)]
        [string]$Controller,

        [Parameter(Mandatory = $true)]
        [string]$Vserver,

        [Parameter(Mandatory = $true)]
        [string]$VolumeName,

        [Parameter(Mandatory = $true)]
        [string]$ShareName,

        [Parameter()]
        [object[]]$InclusionMasks,

        [Parameter()]
        [object[]]$ExclusionMasks
    )

    try {
        $type = [Veeam.Backup.PowerShell.Infos.VBRNASBackupJobObject]
        $bindingFlags = [System.Reflection.BindingFlags]::Instance -bor `
                        [System.Reflection.BindingFlags]::NonPublic -bor `
                        [System.Reflection.BindingFlags]::Public

        # Find the 6-parameter constructor
        $ctor = $type.GetConstructors($bindingFlags) | Where-Object { $_.GetParameters().Count -eq 6 }
        if (-not $ctor) {
            throw "Could not find VBRNASBackupJobObject constructor"
        }

        # Get the fsItemType enum type and value - 'Unknown' is required for NetApp NAS shares
        $fsItemTypeParam = $ctor.GetParameters() | Where-Object { $_.Name -eq 'fsItemType' }
        $fsItemTypeType = $fsItemTypeParam.ParameterType
        $fsItemType = [Enum]::Parse($fsItemTypeType, 'Unknown')

        # Build the Path in NetApp format: <controller>\<vserver>\<volume>\<sharename>:SanSmb
        # Controller, Vserver, and Volume are CASE-SENSITIVE; only ShareName is lowercased
        $path = "{0}\{1}\{2}\{3}:SanSmb" -f $Controller, $Vserver, $VolumeName, $ShareName.ToLower()
        $internalPath = $path

        # Default exclusion masks if not provided
        if (-not $ExclusionMasks) {
            [Veeam.Backup.PowerShell.Infos.VBRNASBackupMask[]]$ExclusionMasks = @(
                New-VBRNASBackupPathMask -Path '\.snapshot'
                New-VBRNASBackupPathMask -Path '\~snapshot'
            )
        }

        if (-not $InclusionMasks) {
            [Veeam.Backup.PowerShell.Infos.VBRNASBackupMask[]]$InclusionMasks = @()
        }

        # Unwrap PSObject wrapper if present - reflection requires the raw .NET object
        $serverObj = if ($Server.psobject.BaseObject) { $Server.psobject.BaseObject } else { $Server }

        # Invoke constructor: (server, path, internalPath, inclusionMasks, exclusionMasks, fsItemType)
        $newObject = $ctor.Invoke(@($serverObj, $path, $internalPath, $InclusionMasks, $ExclusionMasks, $fsItemType))
        return $newObject
    }
    catch {
        Write-Error "Failed to create VBRNASBackupJobObject: $_"
        return $null
    }
}
Usage Example:

Code: Select all

# Get the server object using the Veeam path format (controller\vserver:\sharename)
$veeamPath = "netapp01\svm01:\Finance"
$server = Get-VBRUnstructuredServer -Name $veeamPath

# Create the backup job object with correct SAN path
$nasObject = New-VBRNASBackupJobObjectDirect `
    -Server $server `
    -Controller "netapp01" `
    -Vserver "svm01" `
    -VolumeName "vol_data" `
    -ShareName "Finance"

# Verify the path is correct
$nasObject.Path
# Output: netapp01\svm01\vol_data\finance:SanSmb

# Use the object to create a backup job
$repository = Get-VBRBackupRepository -Name "MyRepository"

Add-VBRNASBackupJob `
    -Name "MyNASBackup" `
    -BackupObject $nasObject `
    -ShortTermBackupRepository $repository `
    -ShortTermRetentionType Days `
    -ShortTermRetentionPeriod 14
Path Format Summary:
  • The standard cmdlet creates UNC paths like \\172.16.x.x\share which don't use SAN integration
  • The correct path format is controller\vserver\volume\sharename:SanSmb
  • Controller, Vserver, Volume = preserve case exactly as they appear in NetApp
  • ShareName = always lowercase
Post Reply

Who is online

Users browsing this forum: Semrush [Bot] and 34 guests