PowerShell advanced function (cmdlet) to get configuration, health, state and backlogs report for DFS-R


I decided to write Windows PowerShell Cmdlet (Advanced Function) to get report of configuration, status and health of Distributed File System Replication (DFS-R) in large enterprise environment (of course it is possible to use it for small deployment). I created this script because there are no native tools for such report and I did not find a single PowerShell script that would satisfy my needs.

Key concepts

  • The script is in PowerShell but the script does not use native DFSR module so it is possible to use it on system with any version of the DFSR module or even without this module.
  • The script was designed to never stop. That means that when there is an error on a single server or on a single replication group, folder or connection then script will log this error and continue with gathering data from other servers, connections, groups or folders.
  • It is possible to target script to a single main (hub) DFS-R server and count backlogs in both directions (from source to destination and from destination to source). Of course it is possible to target all servers and do not count these reverse backlogs.

Examples

Show complete status in GUI

Get-RvDfsrReport -ComputerName fs0.ad.contoso.com, fs1.ad.contoso.com, fs2.ad.contoso.com |
    Out-GridView

Get full DFS-R report for one defined server and for one defined replication group and replicated folder

Get-RvDfsrReport `
    -ComputerName BIGTARGET `
    -GroupName 'USA\FILESERV44*' `
    -FolderName 'Da*' `
    -BacklogCount BothDirections `
    -BacklogCountError 10000 `
    -Remoting CIM -Verbose

<#
Output:
DestinationComputerName : BIGTARGET
SourceComputerName      : FILESERV44
GroupName               : USA\FILESERV44\D
GroupGuid               : AAAAACCC-4444-5555-1100-1111100000AA
GroupDn                 : CN=USA\FILESERV44\D,CN=DFSR-GlobalSettings,CN=System,DC=contoso,DC=com
GroupIsClustered        : False
GroupScheduleInUtc      : True
FolderGuid              : 99999999-1111-2222-0011-2222266CCCCC
FolderName              : Data
FolderRootPath          : X:\FILESERV44\Data
FolderEnabled           : True
FolderReadOnly          : True
FolderConflictSizeInMB  : 4096
FolderStagingSizeInMB   : 8192
FolderFileFilter        : ~*, *.bak, *.tmp
FolderState             : Initial Sync
BacklogCount            : 286917
BacklogReverseCount     : 53603
BacklogCountSummary     : 340520
ReplicationStatus       : False
Status                  : True
Error                   : False
ErrorDescription        :
#>

Get DFS-R groups, folders and connections in a second (skip backlog calculations) for the local server and export it to CSV

Get-RvDfsrReport -BacklogCount Skip -Remoting CIM | Export-Csv `
    -Path 'C:\Temp\My report.csv' `
    -Delimiter "`t" `
    -Encoding UTF8 `
    -NoTypeInformation

Display DFS-R report for multiple servers in a console

Get-RvDfsrReport -ComputerName fs0.ad.contoso.com, fs1.ad.contoso.com, fs2.ad.contoso.com |
    Format-Table -Property GroupName, FolderName, FolderState, BacklogCount, Status

Code

Function Get-RvDfsrReport
{
    <#
    .SYNOPSIS
        Get DFS-R status, health and backlogs. Cmdlet (advanced function) is designed to never stop even when it hit an error so the cmdlet will always produce full report with logged errors.

    .DESCRIPTION
        Developer
            Developer: Rudolf Vesely, http://rudolfvesely.com/
            Copyright (c) Rudolf Vesely. All rights reserved
            License: Free for private use only

            "RV" are initials of the developer's name Rudolf Vesely and distingue names of Rudolf Vesely's cmdlets from the other cmdlets.

        Description
            Get DFS-R status, health and backlogs. Cmdlet (advanced function) is designed to never stop even when it hit an error so the cmdlet will always produce full report with logged errors.

        Requirements
            Developed and tested using PowerShell 4.0.

    .PARAMETER ComputerName
        Name of the remote server to get report.

        If not set then all groups are queried

    .PARAMETER GroupName
        Name of the group to get report. It is possible to use wildcards (for example MyGroup*).

        If not set then all groups will be queried.

    .PARAMETER FolderName
        Name of the group to get report. It is possible to use wildcards (for example MyGroup*).

        If not set then all folders will be queried.

    .PARAMETER BacklogCount
        Possibilities
            Skip
                Fastest method of generating report.

            Get
                Backlogs are calculated for every folder.

            BothDirections
                Backlogs are calculated for every folder in both directorions.

    .PARAMETER BacklogCountError
        Maximum number of backlogs that is evaluated as normal state.

    .PARAMETER Remoting
        Possibilities
            WMI
                Old method using Get-WmiObject that is compatible with PowerShell 2.0 on Windows Server 2012 R2.

            CIM
                More efficient method using Get-CimInstance that is compatible with PowerShell 3.0 on Windows Server 2012 RTM and with later versions.

    .EXAMPLE
        'Get full DFS-R report for one defined server and for one defined replication group and replicated folder'
        Get-RvDfsrReport `
            -ComputerName BIGTARGET `
            -GroupName 'USA\FILESERV44*' `
            -FolderName 'Da*' `
            -BacklogCount BothDirections `
            -BacklogCountError 10000 `
            -Remoting CIM -Verbose

        Output:
        DestinationComputerName : BIGTARGET
        SourceComputerName      : FILESERV44
        GroupName               : USA\FILESERV44\D
        GroupGuid               : AAAAACCC-4444-5555-1100-1111100000AA
        GroupDn                 : CN=USA\FILESERV44\D,CN=DFSR-GlobalSettings,CN=System,DC=contoso,DC=com
        GroupIsClustered        : False
        GroupScheduleInUtc      : True
        FolderGuid              : 99999999-1111-2222-0011-2222266CCCCC
        FolderName              : Data
        FolderRootPath          : X:\FILESERV44\Data
        FolderEnabled           : True
        FolderReadOnly          : True
        FolderConflictSizeInMB  : 4096
        FolderStagingSizeInMB   : 8192
        FolderFileFilter        : ~*, *.bak, *.tmp
        FolderState             : Initial Sync
        BacklogCount            : 286917
        BacklogReverseCount     : 53603
        BacklogCountSummary     : 340520
        ReplicationStatus       : False
        Status                  : True
        Error                   : False
        ErrorDescription        :

    .EXAMPLE
        'Get DFS-R groups, folders and connections in a second (skip backlog calculations) for the local server and export it to CSV'
        Get-RvDfsrReport -BacklogCount Skip -Remoting CIM | Export-Csv `
            -Path 'C:\Temp\My report.csv' `
            -Delimiter "`t" `
            -Encoding UTF8 `
            -NoTypeInformation

    .EXAMPLE
        'Display DFS-R report for multiple servers in a console'
        Get-RvDfsrReport -ComputerName fs0.ad.contoso.com, fs1.ad.contoso.com, fs2.ad.contoso.com |
            Format-Table -Property GroupName, FolderName, FolderState, BacklogCount, Status

    .INPUTS

    .OUTPUTS
        System.Management.Automation.PSCustomObject

    .LINK
        https://techstronghold.com/
    #>

    [CmdletBinding(
        DefaultParametersetName = 'ComputerName',
        SupportsShouldProcess = $true,
        PositionalBinding = $false,
        HelpURI = 'https://techstronghold.com/',
        ConfirmImpact = 'Medium'
    )]

    Param
    (
        [Parameter(
            Mandatory = $false,
            Position = 0,
            ParameterSetName = 'ComputerName'
        )]
        [ValidateLength(1, 255)]
        [string[]]$ComputerName = '.',

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [ValidateLength(1, 255)]
        [string]$GroupName = '*',

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [ValidateLength(1, 255)]
        [string]$FolderName = '*',

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [ValidateSet(
            'Skip',
            'Get',
            'BothDirections'
        )]
        [string]$BacklogCount = 'Get',

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [int64]$BacklogCountError = 100,

        [Parameter(
            Mandatory = $false
            # Position = ,
            # ParameterSetName = ''
        )]
        [ValidateSet(
            'WMI',
            'CIM'
        )]
        [string]$Remoting = 'WMI'
    )

    Begin
    {
        # Configurations
        $ErrorActionPreference = 'Stop'
        if ($PSBoundParameters['Debug']) { $DebugPreference = 'Continue' }
        Set-PSDebug -Strict
        Set-StrictMode -Version Latest

        #region Functions
        Function Get-RvDfsrWmiOrCim
        {
            [CmdletBinding(
                DefaultParametersetName = 'ClassName',
                SupportsShouldProcess = $true,
                PositionalBinding = $false,
                HelpURI = 'https://techstronghold.com/',
                ConfirmImpact = 'Medium'
            )]

            Param
            (
                [Parameter(
                    Mandatory = $true,
                    Position = 0,
                    ParameterSetName = 'ClassName'
                )]
                [AllowNull()]
                [string]$ClassName,

                [Parameter(
                    Mandatory = $false
                    # Position = 0,
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [string]$Filter,

                [Parameter(
                    Mandatory = $false
                    # Position = 0,
                    # ParameterSetName = ''
                )]
                [AllowNull()]
                [string]$ComputerName = '.'
            )

            Begin
            {
                $error                  = $false
                $errorDescription       = ''
                $parametersAndArguments = @{}

                # Local (NetBIOS or FQDN) or remote device
                if ($ComputerName -and
                    $ComputerName -ne '.' -and
                    ($ComputerName -replace '\..*', '') -ne $env:COMPUTERNAME)
                {
                    $parametersAndArguments.Add('ComputerName', $ComputerName)
                }

                # Filter
                if ($Filter)
                {
                    $parametersAndArguments.Add('Filter', $Filter)
                }
            }

            Process
            {
                try
                {
                    if ($Remoting -eq 'WMI')
                    {
                        $output = Get-WmiObject `
                            -Namespace ROOT\MicrosoftDfs `
                            -Class $ClassName `
                            -ErrorAction Stop `
                            @parametersAndArguments
                    }
                    else
                    {
                        $output = Get-CimInstance `
                            -Namespace root/MicrosoftDfs `
                            -ClassName $ClassName `
                            -ErrorAction Stop `
                            @parametersAndArguments
                    }
                }
                catch
                {
                    $output            = $null
                    $error             = $true
                    $errorDescription  = 'Device: {0}; WMI class {0}: Exception: {1}' -f $computerNameItem, $ClassName,  $_.Exception.Message
                    Write-Warning -Message $errorDescription
                }

                if (!$output)
                {
                    $output            = $null
                    $error             = $true
                    $errorDescription  = 'Device: {0}; WMI class {0}: No data' -f $computerNameItem, $ClassName
                    Write-Warning -Message $errorDescription
                }

                # Return
                [PsCustomObject]@{
                    Output            = $output
                    Error             = $error
                    ErrorDescription  = $errorDescription
                }
            }

            End
            {
            }
        }

        Function Get-RvDfsrReportOutput
        {
            [CmdletBinding(
                DefaultParametersetName = 'Data',
                SupportsShouldProcess = $true,
                PositionalBinding = $false,
                HelpURI = 'https://techstronghold.com/',
                ConfirmImpact = 'Medium'
            )]

            Param
            (
                [Parameter(
                    Mandatory = $true,
                    Position = 0,
                    ParameterSetName = 'Data'
                )]
                $Data
            )

            Begin
            {
            }

            Process
            {
                $output = $Data

                if ($Data.ErrorDescription)
                {
                    $output.Status            = $false
                    $output.Error             = $true
                    $output.ErrorDescription  = $Data.ErrorDescription -join '; '
                }
                else
                {
                    $output.ErrorDescription  = $null
                }

                # Return
                $output
            }

            End
            {
            }
        }
        #endregion

        # Modify wildcards to WQL
        if ($GroupName -and $GroupName -ne '*')
        {
            $filterGroupName = $GroupName.Replace('*', '%').Replace('?', '_').Replace('\', '\\')
        }
        else
        {
            $filterGroupName = $null
        }

        if ($FolderName -and $FolderName -ne '*')
        {
            $filterFolderName = $FolderName.Replace('*', '%').Replace('?', '_').Replace('\', '\\')
        }
        else
        {
            $filterFolderName = $null
        }
    }

    Process
    {
        foreach ($computerNameItem in $ComputerName)
        {
            <#
            Variables
            #>

            $output = [PsCustomObject]@{
                DestinationComputerName   = $computerNameItem
                SourceComputerName        = $null

                GroupName                 = $null
                GroupGuid                 = $null
                GroupDn                   = $null
                GroupIsClustered          = $null
                GroupScheduleInUtc        = $null

                FolderGuid                = $null
                FolderName                = $null
                FolderRootPath            = $null
                FolderEnabled             = $null
                FolderReadOnly            = $null
                FolderConflictSizeInMB    = $null
                FolderStagingSizeInMB     = $null
                FolderFileFilter          = $null

                # Normal, Initial replication, etc.
                FolderState               = $null

                # Backlog: Source -> Destination
                BacklogCount              = $null

                # Backlog: Destination -> Source
                BacklogReverseCount      = $null

                # Backlog: Sum of all directions
                BacklogCountSummary       = $null

                # If $false then replication is not in normal state or backlog is high
                ReplicationStatus         = $null

                # $false on any error during gtrial to get and process data
                Status                    = $true

                # $true on any error during gtrial to get and process data
                Error                     = $false
                ErrorDescription          = @()
            }



            <#
            Groups
            #>

            $parametersAndArguments = @{}
            if ($filterGroupName)
            {
                $parametersAndArguments.Add('Filter', ("ReplicationGroupName LIKE '{0}'" -f $filterGroupName))
            }

            $groupConfigurationItems = Get-RvDfsrWmiOrCim `
                -ClassName 'DfsrReplicationGroupConfig' `
                -ComputerName $computerNameItem `
                @parametersAndArguments

            if ($groupConfigurationItems.Error)
            {
                Write-warning -Message 'Continue with another device'

                $output.ErrorDescription += $groupConfigurationItems.ErrorDescription

                # Return
                Get-RvDfsrReportOutput -Data $output
            }
            else
            {
                foreach ($groupConfigurationItem in $groupConfigurationItems.Output)
                {
                    <#
                    Group configurations
                    #>

                    try
                    {
                        Write-Verbose -Message ('    - Group name: {0}' -f $groupConfigurationItem.ReplicationGroupName)

                        $output.GroupName                = $groupConfigurationItem.ReplicationGroupName
                        $output.GroupGuid                = $groupConfigurationItem.ReplicationGroupGuid
                        $output.GroupDn                  = $groupConfigurationItem.ReplicationGroupDn.Replace('\\', '\')
                        $output.GroupIsClustered         = $groupConfigurationItem.IsClustered
                        $output.GroupScheduleInUtc       = $groupConfigurationItem.DefaultScheduleInUtc
                    }
                    catch
                    {
                        $message = 'Cannot get group configuration'
                        Write-warning -Message $message

                        $output.ErrorDescription += $message
                    }



                    <#
                    Folders
                    #>

                    $parametersAndArguments = @{}
                    if ($filterFolderName)
                    {
                        $filterAdd = " AND ReplicatedFolderName LIKE '{0}'" -f $filterFolderName
                    }
                    else
                    {
                        $filterAdd = ''
                    }

                    $folderConfigurationItems = Get-RvDfsrWmiOrCim `
                        -ClassName 'DfsrReplicatedFolderConfig' `
                        -Filter ("ReplicationGroupGuid = '{0}'{1}" -f $groupConfigurationItem.ReplicationGroupGuid, $filterAdd) `
                        -ComputerName $computerNameItem

                    $connectionConfigurationItems = Get-RvDfsrWmiOrCim `
                        -ClassName 'DfsrConnectionConfig' `
                        -Filter ("Inbound = 'True' AND ReplicationGroupGuid = '{0}'" -f $groupConfigurationItem.ReplicationGroupGuid) `
                        -ComputerName $computerNameItem

                    if ($folderConfigurationItems.Error)
                    {
                        Write-warning -Message 'Continue with another group'

                        $output.ErrorDescription += $folderConfigurationItems.Error

                        # Return
                        Get-RvDfsrReportOutput -Data $output
                    }
                    else
                    {
                        foreach ($folderConfigurationItem in $folderConfigurationItems.Output)
                        {
                            <#
                            Folder configurations
                            #>

                            try
                            {
                                Write-Verbose -Message ('        - Folder name: {0}' -f $folderConfigurationItem.ReplicatedFolderName)

                                $output.FolderName               = $folderConfigurationItem.ReplicatedFolderName
                                $output.FolderGuid               = $folderConfigurationItem.ReplicatedFolderGuid
                                $output.FolderRootPath           = $folderConfigurationItem.RootPath
                                $output.FolderEnabled            = $folderConfigurationItem.Enabled
                                $output.FolderReadOnly           = $folderConfigurationItem.ReadOnly
                                $output.FolderConflictSizeInMB   = $folderConfigurationItem.ConflictSizeInMb
                                $output.FolderStagingSizeInMB    = $folderConfigurationItem.StagingSizeInMb
                                $output.FolderFileFilter         = $folderConfigurationItem.FileFilter
                            }
                            catch
                            {
                                $message = 'Cannot get folder configuration'
                                Write-Warning -Message $message

                                $output.ErrorDescription += $message
                            }



                            <#
                            Folder information
                            #>

                            $folderInformationItem = Get-RvDfsrWmiOrCim `
                                -ClassName 'DfsrReplicatedFolderInfo' `
                                -Filter ("ReplicationGroupGUID = '{0}' AND ReplicatedFolderGuid = '{1}'" -f $groupConfigurationItem.ReplicationGroupGuid, $folderConfigurationItem.ReplicatedFolderGuid) `
                                -ComputerName $computerNameItem

                            if ($folderInformationItem.Error)
                            {
                                $message = 'Cannot get folder information'
                                Write-Warning -Message $message

                                $output.ErrorDescription += $message
                            }
                            else
                            {
                                try
                                {
                                    switch ($folderInformationItem.Output.State)
                                    {
                                        0        { $output.FolderState = 'Uninitialized';   break }
                                        1        { $output.FolderState = 'Initialized';     break }
                                        2        { $output.FolderState = 'Initial Sync';    break }
                                        3        { $output.FolderState = 'Auto Recovery';   break }
                                        4        { $output.FolderState = 'Normal';          break }
                                        5        { $output.FolderState = 'In Error';        break }
                                        Default  { $output.FolderState = 'Unknown'                }
                                    }

                                    if ($folderInformationItem.Output.State -eq 4)
                                    {
                                        $output.ReplicationStatus = $true
                                    }
                                    else
                                    {
                                        $output.ReplicationStatus = $false
                                    }
                                }
                                catch
                                {
                                    $message = 'Cannot get folder information'
                                    Write-warning -Message $message

                                    $output.ErrorDescription += $message
                                }
                            }



                            <#
                            Connections
                            #>

                            if ($connectionConfigurationItems.Error)
                            {
                                Write-warning -Message 'Continue with another group'

                                $output.ErrorDescription += $connectionConfigurationItems.ErrorDescription

                                # Return
                                Get-RvDfsrReportOutput -Data $output
                            }
                            else
                            {
                                foreach ($connectionConfigurationItem in $connectionConfigurationItems.Output)
                                {
                                    $partnerComputerName  = $connectionConfigurationItem.PartnerName

                                    Write-Verbose -Message ('            - Connection: {0} < - {1}' -f $computerNameItem, $partnerComputerName)

                                    $output.SourceComputerName = $partnerComputerName


                                    <#
                                    Backlog count
                                    #>

                                    if ($BacklogCount -ne 'Skip')
                                    {
                                        $folderInformationPartnerItem = Get-RvDfsrWmiOrCim `
                                            -ClassName 'DfsrReplicatedFolderInfo' `
                                            -Filter ("ReplicationGroupGUID = '{0}' AND ReplicatedFolderName = '{1}'" -f $groupConfigurationItem.ReplicationGroupGuid, $folderConfigurationItem.ReplicatedFolderName) `
                                            -ComputerName $partnerComputerName

                                        if ($folderInformationPartnerItem.Error)
                                        {
                                            Write-warning -Message 'Continue with another connection'

                                            $output.ErrorDescription += $folderInformationPartnerItem.ErrorDescription

                                            # Return
                                            Get-RvDfsrReportOutput -Data $output
                                        }
                                        else
                                        {
                                            <#
                                            Direction: Normal
                                            #>

                                            $errorBacklogDescription = $null

                                            try
                                            {
                                                $versionVector = $folderInformationPartnerItem.Output.GetVersionVector().VersionVector
                                            }
                                            catch
                                            {
                                                $errorBacklogDescription = 'Exception during trial to get version vector to get number of backlogs: {0}' -f $_.Exception.Message
                                                Write-Warning -Message $errorBacklogDescription
                                            }

                                            if (!$errorBacklogDescription)
                                            {
                                                try
                                                {
                                                    $output.BacklogCount = [int64]$folderInformationItem.Output.GetOutboundBacklogFileCount($versionVector).BacklogFileCount
                                                }
                                                catch
                                                {
                                                    $errorBacklogDescription = 'Exception during trial to get number of backlogs: {0}' -f $_.Exception.Message
                                                    Write-Warning -Message $errorBacklogDescription
                                                }
                                            }

                                            if ($errorBacklogDescription)
                                            {
                                                $output.ErrorDescription += $errorBacklogDescription
                                            }



                                            <#
                                            Direction: Reverse
                                            #>

                                            if ($BacklogCount -eq 'BothDirections')
                                            {
                                                $errorBacklogDescription = $null

                                                try
                                                {
                                                    $versionVector = $folderInformationItem.Output.GetVersionVector().VersionVector
                                                }
                                                catch
                                                {
                                                    $errorBacklogDescription = 'Exception during trial to get version vector to get number of backlogs in reverse direction: {0}' -f $_.Exception.Message
                                                    Write-Warning -Message $errorBacklogDescription
                                                }

                                                if (!$errorBacklogDescription)
                                                {
                                                    try
                                                    {
                                                        $output.BacklogReverseCount = [int64]$folderInformationPartnerItem.Output.GetOutboundBacklogFileCount($versionVector).BacklogFileCount
                                                    }
                                                    catch
                                                    {
                                                        $errorBacklogDescription = 'Exception during trial to get number of backlogs in reverse direction: {0}' -f $_.Exception.Message
                                                        Write-Warning -Message $errorBacklogDescription
                                                    }
                                                }

                                                if ($errorBacklogDescription)
                                                {
                                                    $output.ErrorDescription += $errorBacklogDescription
                                                }
                                            }



                                            <#
                                            Direction: Summary
                                            #>

                                            if ($output.BacklogCount -ne $null -and $output.BacklogReverseCount -ne $null)
                                            {
                                                $output.BacklogCountSummary = $output.BacklogCount + $output.BacklogReverseCount
                                            }



                                            <#
                                            Replication status
                                            #>

                                            if ($output.BacklogCount -gt $BacklogCountError -or $output.BacklogReverseCount -gt $BacklogCountError)
                                            {
                                                $output.ReplicationStatus = $false
                                            }
                                        }
                                    }

                                    # Return
                                    Get-RvDfsrReportOutput -Data $output
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    End
    {
    }
}

5 responses to “PowerShell advanced function (cmdlet) to get configuration, health, state and backlogs report for DFS-R”

  1. Rudolf – I’m testing this script against a Server 2012 box and get no output (Nor any errors.) Just immediately takes me back to the powershell prompt. I’m running this under PS 3.0. I tried your example against the local server and nothing – Get-RvDfsrReport -ComputerName fs0.ad.contoso.com

  2. Hi Rudolf, excellent script thank you very much for sharing. @Pat this is function use it as module if want to use from any PS prompt

  3. Hi, I just tested the script and it works fine … But I tested it on a DFSR that I know was in trouble and it still gave a Status = True although the dsfrdiag told me it was in trouble. How can we amend the script to reflect this ?

  4. Just trying out the script on a Windows 2012 R2 server and I’m getting back errors where it seems to be mapping the server name to the WMI class : Device: servername; WMI class servername: No data Any ideas ?

Leave a Reply

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

Active Directory Advanced function AlwaysOn Availability Groups AlwaysOn Failover Cluster Instances Building Cloud Cloud Cluster Cmdlet Database Deployment Design DFS Domain Controller DSC Fabric Failover Clustering File Server Group Policy Hardware Profile Host Hyper-V Installation Library Library Asset Library Server Network Operations Manager Orchestrator PowerShell PowerShell User Group PowerShell Workflow Security Service Manager SQL Server Storage System Center Template Time Time Synchronization Tips Virtual Machine Virtual Machine Manager VM Network VM Template Windows Server 2012 R2