Modifying GPO Printer Deployment using PowerShell

We use the Deployed Printers feature in the Print Management console to deploy printers to users. As part of the printer migration that I have been working on, I needed to modify all of these GPOs so that all of the policies directed all of the users to the new print server.

There are over 200 policies in existence. That would be a lot of manual work, that would be prone to errors.

Also, I needed to be able to change over print servers in around an hour. Editing 200 policies in an hour would be impossible.

This is what the result of this conundrum is. The script below goes out to find all of the deployed printers in all GPOs in the domain. It then looks for the name of the old print server, and replaces it with the name of the new print server.

The script fully supports the -WhatIf switch, and also an output of all of the printers that were found and what changes were made is output at the end of the process. I suggest that the output is piped to a variable, which can then be formatted as a table or exported into something more readable. I just provide the raw data here, you can do with it as you wish.

You will need the ActiveDirectory module loaded in your Powershell session, and you will also need to be running this script as a user that has sufficient permissions to modify GPO in your domain.

As always, test on a lab before running this on your production servers!

# ----------------------------------------------------------------------------------------------------------
# PURPOSE:    Modify all GPO's which have Pushed Printer Connections to a new print server name.
#
# VERSION     DATE         USER                DETAILS
# 1           22/08/2014   Craig Tolley        First version
#
#
# ----------------------------------------------------------------------------------------------------------

#Define the print server names. Should include the leading \\ to ensure it only matches at the start.
Function Modify-PushedPrinterConnections
{
[cmdletbinding(SupportsShouldProcess=$True)]

Param
    (
        #The name of the Old Print Server. This string will be searched for in order to be replaced.
        [Parameter(Mandatory=$true)]
        [string]$OldPrintServerName,

        #The name of the New Print Server. This will replace the Old Print Server value.
        [Parameter(Mandatory=$true)]
        [string]$NewPrintServerName
    )

#Collection detailing all of the work
$GPOPrinterDetails = @()

#Get all of the GPO objects in the domain.
$GPOs = Get-GPO -All
Write-Host "GPOs Retrieved: $($GPOs.Count)"


ForEach ($GPO in $GPOs)
{
    $PrintObjects = Get-ADObject -SearchBase "CN={$($GPO.Id)},CN=Policies,CN=System,DC=medlan,DC=cam,DC=ac,DC=uk" -Filter {objectClass -eq "msPrint-ConnectionPolicy"} -SearchScope Subtree
    
    ForEach ($PCO in $PrintObjects)
    {
        #Get the properties of the Print Connection Object that we actually need.
        $PrintConnection = Get-ADObject $PCO.DistinguishedName -Properties printerName, serverName, uNCName
    
        #Log details of the policy that we have found    
        $GPOPrinterDetail = @{
                    GPOId = $GPO.Id
                    GPOName = $GPO.DisplayName
                    PrintConnectionID = $PrintConnection.ObjectGUID
                    PrinterName = $PrintConnection.printerName
                    OriginalPrintServer = $PrintConnection.serverName
                    OriginalUNCName = $PrintConnection.uNCName
                    NewPrintServer = $null
                    NewUNCName = $null
                    ChangeStatus = "NotEvaluated"
                    }
        
        #Find out if we need to make a change or not.
        If ($PrintConnection.serverName.ToLower() -eq $OldPrintServerName.ToLower())
        {
            #Change the local instance
            $PrintConnection.serverName = $NewPrintServerName
            $PrintConnection.uNCName = $PrintConnection.uNCName.Replace($OldPrintServerName,$NewPrintServerName)
            
            #Update our reporting collection
            $GPOPrinterDetail.NewPrintServer = $PrintConnection.serverName
            $GPOPrinterDetail.NewUNCName = $PrintConnection.uNCName
            $GPOPrinterDetail.ChangeStatus = "ChangePending"
                        
            #Write the changes and catch any errors
            Try
                {Set-ADObject -Instance $PrintConnection -Verbose
                $GPOPrinterDetail.ChangeStatus = "ChangeSuccess"}
            Catch
                {$GPOPrinterDetail.ChangeStatus = "ChangeFailed"}
                
        }
        Else
        {
            $GPOPrinterDetail.ChangeStatus = "NoChange"
        }

        #Update the table
        $GPOPrinterDetails += New-Object PSObject -Property $GPOPrinterDetail
    }
 
}

#Finally write out the changes
Write-Output $GPOPrinterDetails

}

Be the first to like.
Posted in Active Directory, Powershell, Server 2012 | Leave a comment

Windows 2012: PrintService EventID 812 Error 0x5

Been working on a print server migration, and with the cutover we started noticing a lot of these errors in the PrintService Operational Log. This log has to be enabled before you will get these errors if you have not done so already.

 Event ID 812: Print Spool 0x5 ErrorAs part of the setup of this print server, a dedicated disk was attached to the system for the print spool in order to ensure that disk was not a bottleneck in print performance. Windows was left to create the new folder in the new location after changing the path in the Print Management console.

It turns out however that the permissions that Windows by default applies to the newly created folder aren’t actually enough.

Despite this particular error directly stating that the spooler failed to delete the file, the issue actually is that the spooler was failing to create the file first. Therefore once the job had completed the file was not there to delete.

I never found out where the spooler service was putting the files instead, assuming here that it continued to use the spool files. They were not being stored in the default C:\windows\system32\spool folder.

Our solution, was to add in the Authenticated Users group with Full Control permissions to the newly created spool folder.

Monitoring the folder after making this change, which then cleared the error, shows that the spool files are created. The files are created with the Owner being set to SYSTEM. The SYSTEM user had Full Control of the folder anyway. The delete file command seemed to be being run in the context of the user that printed the job. As that user was not the owner, the CREATOR OWNER permission did not suffice for removing the file.

Be the first to like.
Posted in Server 2012 | Leave a comment

Clean Up Unused Printer Drivers

Working on a printer migration project, and one of the first steps was to clean up the existing print servers so that when running the migration. I wanted a way to contact remote print servers too. This script, once imported allows cleanup of remote print servers.

Requires Powershell 4, so run off a Windows 8/Windows Server 2012.

If targeting a Windows 2003 print server, it cannot perform the actual removal and will just list a number of errors. It does atleast highlight all of the drivers which are not in use though.

# ----------------------------------------------------------------------------------------------------------
# PURPOSE: Remove Printer Drivers which do not have any printers
#
# VERSION DATE USER DETAILS
# 1 28/08/2014 Craig Tolley First version
# 1.1 29/08/2014 Craig Tolley Moved getting printers into variable to improve performance.
#
# ----------------------------------------------------------------------------------------------------------

#Define the print server names.
Function Remove-UnusedPrinterDrivers
{

Param
(
#The name of the print server to clean up.
[Parameter(Mandatory=$true)]
[string]$PrintServerName
)

#Get all of the printer drivers
$Drivers = Get-PrinterDriver -ComputerName $PrintServerName
$Printers = Get-Printer -ComputerName $PrintServerName

ForEach($Driver in $Drivers)
{
$PrintersUsingDriver = ($Printers | Where {$_.DriverName -eq $Driver.Name} | Measure).Count
Write-Host "Driver: $($Driver.Name) has $PrintersUsingDriver printers using it."

If ($PrintersUsingDriver -eq 0)
{
Try
{
Remove-PrinterDriver -Name $Driver.Name -ComputerName $PrintServerName -ErrorAction Stop
Write-Host " $($Driver.Name) has been removed." -ForegroundColor Green
}
Catch
{
Write-Host " Failed to remove driver: $($Driver.Name)" -ForegroundColor Red
}
}
Write-Host ""
}
}

Be the first to like.
Posted in Powershell, Server 2003, Server 2008, Server 2012 | Leave a comment

WhatsUp Gold Raspberry Pi Monitor

I have worked out a way to get a full screen display of a specific dashboard for use with a digital signage display using a Raspberry Pi, using only a web browser.

The Dashboard client was ok, but required a Windows machine, and although we could use the existing web page with automatic login (http://community.whatsupgold.com/forums/whatsupgoldeditionsstandardandpremiumeditions/automatic-login-by-url) too much space was lost using the menu and title bars.

So, here it is:

  1. On your WUG server, browse to the installation directory: C:\Program Files (x86)\Ipswitch\WhatsUp\HTML\NmConsole\Workspace\HomeWorkspace
  2. Take a copy of HomeWorkspace.asp and call it HomeWorkspaceMonitor.asp
  3. Open the file in your favourite editor and remove the following sections:
    1. AddNavigation();
    2.  <% AddWorkspaceViewTabs(WT_UNIVERSAL); %>
    3.  <div id=”titlebar”>
      ‘ <h2><%= $.tr(“Home”) %></h2>
      ‘ <% AddWorkspaceToolbarButtons($.tr(“Home Dashboard”), WT_UNIVERSAL) %>
      </div>
  4. Save the file.
  5. Open up your browser and enter the URL, substituting your server name, the ID of the workspace you want to view and a valid username and password: http://localhost/NmConsole/Workspace/HomeWorkspace/HomeWorkspaceMonitor.asp?HomeWorkspace.nWorkspaceID=10025&sUsername=DisplayRead&sPassword=DisplayRead

This gave us a page with only the actual content of the dashboard. We set the user account to have very limited access, and then set a Raspberry Pi to autoload Firefox, full screen and browse to this page.

WUG.png

Hopefully this will be useful to others

Be the first to like.
Posted in Monitoring, Raspberry Pi | Leave a comment

Renaming NetApp SnapMirror Destinations

We have a collection of NetApp devices hosting a large number of volumes, and utilise SnapMirrors extensively for backup and DR, as well as moving information between departments

As the storage has grown, and different people have added new volumes, new SnapMirrors have been created, with a wide variety of naming formats to match the mood of the creator on that particular day. This makes managing volumes, when you are not exactly sure what they do, a little challenging.

A standard for the naming of these volumes was decided on, and all new SnapMirror destinations must be in the format of the original volume name with _SnapMirror appended to the end. This is similar to what the OnCommand System centre uses, just without the date.

Whilst this was all good for new volumes, the 200 existing volumes and mirrors would likely not be changed for a long time. So, I set about looking for a way to update all of our SnapMirror destinations. With there being a lot of volumes, scripting was the only way to be looking at this.

So, here is a module containing two functions. The first, Get-NonStandardSnapMirrorNames looks through all of the specified NetApp Filers for SnapMirrors do not fall into the format specified. The second function, Repair-NonStandardSnapMirrorNames, runs through a procedure which can correct them.

I have been good and even included PowerShell help inside the function, so once you have imported the module you can run Get-Help with the relevant function in order to find out the exact syntax.

If you use this and have some suggestions on how it could be improved then please let me know. I am relatively new to NetApp storage devices and still working out some of the peculiarities. There are probably a number of ways that this could be improved, but it worked well for me in the environment that I have,

You will need to have the DataOnTap PowerShell kit available from NetApp to use this script, as everything is performed using the PowerShell commands rather than using any SSH sessions.

So, here it is. Copy into your favourite editor, save, import to PowerShell and off you go.

# ----------------------------------------------------------------------------------------------------------
# PURPOSE:    Check all SnapMirrors on Filers to ensure that they are named correctly. 
#
# VERSION     DATE         USER                DETAILS
# 1           09/07/2014   Craig Tolley        First version
# 1.1         15/07/2014   Craig Tolley        Moved Disables/Enables Snapmirrors into a batch to stop failed transfers
#                                              Made Common Credentials a parameter that then asks for details. 
#
# ----------------------------------------------------------------------------------------------------------

<# .Synopsis    Find all SnapMirrors where the destination volume name does not match the specified convention. Returns a collection of SnapMirrors.  .EXAMPLE    Get-NonStandardSnapMirrorNames -FilersToCheck "filer1", "filer2"    Provide a list of the filers to check using the default suffix of _SnapMirror.    e.g This would be considered a valid destination.          Source:       vol1         Destination:  vol1_SnapMirror    e.g This would be considered invalid.          Source:       vol1         Destination:  vol1_SM .EXAMPLE    Get-NonStandardSnapMirrorNames -FilersToCheck "filer1", "filer2" -SnapMirrorSuffix "_Snap"        Provide a list of the filers to check. Also specify what the SnapMirror suffix should be.     In this case it would check to see if the destination was the name of the source with _Snap appended to the end.  .EXAMPLE    Get-NonStandardSnapMirrorNames -FilersToCheck "filer1", "filer2" -CommonCredentials        Provide a list of the filers to check and that all filers use the same credentials. You will only be prompted once for credentials      #>
function Get-NonStandardSnapMirrorNames
{
    [CmdletBinding()]
    Param
    (
        # A collection of the filers that we want to check.
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [string[]]$FilersToCheck,

        # The suffix that you would expect to have found in a properly named SnapMirror relationship. By default it expects _SnapMirror, this can be overridden. 
        # e.g If the source volume is called 'MyVol', would mean that we expect the destination volume to be 'MyVol_SnapMirror'
        [string]$SnapMirrorSuffix = "_SnapMirror",

        #Credentials to be used for every filer that you are connecting to. If each filer requires different credentials then leave this option out. 
        [Switch]$CommonCredentials

    )

    Begin
    {
        #Check that that DataOnTap Module is available, check it is loaded. 
        Write-Host "Checking for DataONTAP Module..."
        if ((Get-Module -ListAvailable | Where {$_.Name -eq "DataONTAP"}).Count -ne 1) {
            Write-Error "DataONTAP not installed on targeted machine"
            Exit 1
            }
        if ((Get-Module | Where {$_.Name -eq "DataONTAP"}).Count -ne 1) {
            Write-Host "Importing DataONTAP Module..."
            Import-Module -Name "DataONTAP" | Out-Null
            }

        #Create a new collection of Snapmirrors
        $SnapmirrorsToFix = @()

        #Get credentials for all of the filers - to save constantly asking for them later
        if ($CommonCredentials) {$Credentials = Get-Credential}
        foreach($Filer in $FilersToCheck)
            {
            if ($CommonCredentials)
                {Add-NaCredential -Controller $Filer -Credential $Credentials | Out-Host}
            else
                {Add-NaCredential -Controller $Filer -Credential (Get-Credential -Message "Please supply credentials for $filer") | Out-Host}
            }
    }

    Process
    {
        Write-Host "Getting all SnapMirrors that do not match the specified convention"
        Write-Host "Set SnapMirror Suffix is: $SnapMirrorSuffix"
        Write-Host " " 

        #Go through each of the filers in turn and look at all of the volumes and all of the SnapMirrors
        foreach($Filer in $FilersToCheck)
            {
            #Connect to the filer and get all of the volumes
            Write-Host " "
            Write-Host "Connecting to $Filer..."
            Connect-NaController $Filer | Out-Host
            $VolCount = 0
            
            $Volumes = Get-NaVol

            foreach($Volume in $Volumes)
                {
                #Get the snapmirrors for the volume where it is the source of the mirror
                $SnapMirrors = Get-NaSnapmirror -Location $Volume | Where {$_.State -eq "source"}

                foreach($SnapMirror in $SnapMirrors)
                    {
                        [string]$DestinationFilerString = $SnapMirror.Destination.Split(":")[0]
                        [string]$DestinationVolumeString = $SnapMirror.Destination.Split(":")[1]
                        [string]$SourceVolumeName = -Join  ("$($Volume.Name)","$SnapMirrorSuffix")

                        If($DestinationVolumeString -ne $SourceVolumeName)
                            {
                                $SnapmirrorsToFix += $SnapMirror
                                $VolCount += 1
                            }
                    }
                }
                Write-Host " "
                Write-Host "$VolCount volumes found on $Filer that do not match the specified convention"
            }

    }
    
    End
    {
    Write-Output $SnapmirrorsToFix
    Write-Host " "
    Write-Host "  --  Completed finding SnapMirrors that do not match the convention  -- "
    }
}


<# .Synopsis    Re-configures all of the provided Snapmirrors to match the conventions. Pass in a Snapmirror object.    Updates the volume name, the SnapMirror schedule on the destination filer, the SnapMirror relationship on the source filer    Forces a re-sync to ensure that everything is working.     Requires an initial baseline to exist before the operation will complete. Also the SnapMirror must be Idle.  .EXAMPLE    Repair-NonStandardSnapMirrorNames -SnapMirrorsToFix $SnapMirror .EXAMPLE    Repair-NonStandardSnapMirrorNames -SnapMirrorsToFix $SnapMirror -SnapMirrorSuffix "_Snap" .EXAMPLE    Repair-NonStandardSnapMirrorNames -SnapMirrorsToFix $SnapMirror -CommonCredentials    Supply one set of credentials for use with all the filers that will be connected to. If the filers require different credentials, then this parameter should be omitted.     If omitted you will be prompted for credentials for each of the filers that are required to be connected to.  #>
function Repair-NonStandardSnapMirrorNames
{
    [CmdletBinding()]
    Param
    (
        # A collection of the filers that we want to check.
        [Parameter(Mandatory=$true,
                   ValueFromPipeline=$false,
                   Position=0)]
        [DataONTAP.Types.Snapmirror.SnapmirrorStatusInfo[]]$SnapMirrorsToFix,

        # The suffix that you would expect to have found in a properly named SnapMirror relationship. By default it expects _SnapMirror, this can be overridden. 
        # e.g If the source volume is called 'MyVol', would mean that we expect the destination volume to be 'MyVol_SnapMirror'
        [string]$SnapMirrorSuffix = "_SnapMirror",

        #Credentials to be used for every filer that you are connecting to. If each filer requires different credentials then leave this option out. 
        [Switch]$CommonCredentials
    )

    Begin
    {
        #Check that that DataOnTap Module is available, check it is loaded. 
        Write-Host "Checking for DataONTAP Module..."
        if ((Get-Module -ListAvailable | Where {$_.Name -eq "DataONTAP"}).Count -ne 1) {
            Write-Error "DataONTAP not installed on targeted machine"
            Exit 1
            }
        if ((Get-Module | Where {$_.Name -eq "DataONTAP"}).Count -ne 1) {
            Write-Host "Importing DataONTAP Module..."
            Import-Module -Name "DataONTAP" | Out-Null
            }
            
            [DataONTAP.Types.Snapmirror.SnapmirrorStatusInfo[]]$Snapmirror

            #Connect to all of the necessary filers in advance. Controllers are then explicitly defined for each operation. 
            $Filers = @()
            foreach ($Snapmirror in $SnapMirrorsToFix)
                {
                $Filers += $SnapMirror.Source.Split(":")[0]
                $Filers += $Snapmirror.Destination.Split(":")[0]
                }
            $Filers = $Filers | Select -Unique

            #Get credentials for all of the filers - to save constantly asking for them later
            if ($CommonCredentials) {$Credentials = Get-Credential}
            foreach($Filer in $Filers)
            {
                if ($CommonCredentials)
                    {Add-NaCredential -Controller $Filer -Credential $Credentials | Out-Host}
                else
                    {Add-NaCredential -Controller $Filer -Credential (Get-Credential -Message "Please supply credentials for $filer") | Out-Host}
                }
    }

    Process
    {

        #Disable SnapMirrors on all Destination Filers
        $Filers = @()
        foreach ($Snapmirror in $SnapMirrorsToFix)
            {$Filers += $Snapmirror.Destination.Split(":")[0]}
        $Filers = $Filers | Select -Unique
        Write-Host "Disabling SnapMirrors on the destination so that they are not updated during the rename"
        foreach($Filer in $Filers)
        {
            Write-Host $Filer
            Connect-NaController $Filer | Out-Null
            Disable-NASnapMirror  | Out-Null
        }
   

        #Perform the actual repair operation. 
        foreach($Snapmirror in $SnapmirrorsToFix)
            {   
                Write-Host "Fixing SnapMirror destination name"    
                Write-Host $Snapmirror

                #Set all of the names for the sources and the destinations. Easier than keeping calculating them on the fly. 
                $SourceFiler = $Snapmirror.Source.Split(":")[0]
                $SourceVolName = $Snapmirror.Source.Split(":")[1]
                $DestinationFiler = $Snapmirror.Destination.Split(":")[0]
                $OriginalDestinationVolName = $Snapmirror.Destination.Split(":")[1]
                $NewDestinationVolName = -Join ($Snapmirror.Source.Split(":")[1],"$SnapMirrorSuffix")
                
                #Connect to the Source
                Write-Host "Connecting to the source filer"
                Connect-NaController $SourceFiler
                $Snapmirror = Get-NaSnapmirror $Snapmirror.Destination.Split(":")[1]

                #Check that the source volume is attached to vfiler0. If not, editing becomes a lot harder and not implemented here! :-)
                If ((Get-NAVol $SourceVolName).OwningVfiler -ne "vfiler0")
                    {
                    Write-Error "The source volume is not owned by the default vFiler. Cannot edit SnapMirrors that are owned by non-default vFilers. Stopping change for this SnapMirror"
                    Break
                    }

                #Check that the snapmirror is not actually running. If it is, then break. 
                If ($Snapmirror.Status -ne "idle")
                    {
                    Write-Error "The snapmirror is not idle. Cancelling operation"
                    Break
                    }

                #We must have at least one base snapshot, else the release and the update will fail as no common baseline exists. 
                If (($Snapmirror.BaseSnapshot).ToString -eq "")
                    {
                    Write-Error "The snapmirror relationship does not have a common base snapshot. Operation would break the current relationship"
                    Break
                    }

                #Check whether the destination is already in the correct format. 
                If ($NewDestinationVolName -eq $OriginalDestinationVolName)
                    {
                    Write-Host "Snapmirror Destination is already in the correct format. Not going to change anything"
                    Break
                    }
                
                #Release the Snapmirror - which removes it from the Source Filer
                Write-Host "Releasing the SnapMirror source connection"
                Connect-NaController $SourceFiler | Out-Null
                Get-NASnapMirror $OriginalDestinationVolName | Invoke-NaSnapmirrorRelease -Confirm:$false | Out-Null
                          
                Try
                    {
                    Connect-NaController $DestinationFiler | Out-Null
                    #Rename the destination Vol
                    Write-Host "Renaming the destination volume"
                    Get-NaVol -Name $OriginalDestinationVolName | Rename-NaVol -NewName $NewDestinationVolName | Out-Null
      
                    #Update the Destination of the Snapmirror on the Destination Filer
                    Write-Host "Updating the SnapMirror Schedule with the new destination volume name"
                    $SMSched = Get-NaSnapmirrorSchedule $OriginalDestinationVolName
                    $SMSched.Destination = "$DestinationFiler`:$NewDestinationVolName"
                    $SMSched | Set-NaSnapmirrorSchedule

                    #Remove the Old SnapMirror Schedule
                    Write-Host "Removing the SnapMirror schedule for the old destination"
                    Get-NaSnapmirrorSchedule $OriginalDestinationVolName | Remove-NaSnapmirrorSchedule | Out-Null          
                    }
                
                Catch
                    {   
                    Write-Error "Failed to update the volume"
                    Break
                    }
            }

        #Re-enable SnapMirrors on all Destination Filers
        Write-Host "Re-enabling SnapMirrors on the destination..."
        foreach($Filer in $Filers)
        {
            Write-Host $Filer
            Connect-NaController $Filer | Out-Null
            Enable-NASnapMirror  | Out-Null
        }

        #Update all Snapmirrors so that they re-register on the Source Filer
        foreach($Snapmirror in $SnapmirrorsToFix)
            {
            Write-Host "Registering the SnapMirror on the Source filer and reconnecting the SnapMirror"
            $NewDestinationVolName = -Join ($Snapmirror.Source.Split(":")[1],"$SnapMirrorSuffix")
            $DestinationFiler = $Snapmirror.Destination.Split(":")[0]
            Connect-NaController $DestinationFiler | Out-Null
            Do {Write-Host "."} Until ((Get-NASnapMirror $NewDestinationVolName).Count)
            Get-NASnapMirror $NewDestinationVolName | Invoke-NaSnapmirrorBreak -Confirm:$false | Out-Null
            Get-NASnapMirror $NewDestinationVolName | Invoke-NaSnapmirrorResync -Confirm:$false | Out-Host
            }

        #Update any Snapmirrors that might be in a failed State. The Disable above does not check for any that were already running.
        Write-Host "Resuming any SnapMirrors that might have failed due to SnapMirror being Disabled. "
        foreach($Filer in $Filers)
        {
            Connect-NaController $Filer | Out-Null
            Get-NaSnapmirror | Where {$_.CurrentTransferError -ne $null -and $_.Status -eq "idle"} | Invoke-NaSnapmirrorUpdate
        }

    }
    
    End
    {
        Write-Host "  --  Completed repairing SnapMirrors that do not match the convention  -- "
    }
}

Be the first to like.
Posted in General Stuff, Powershell | Leave a comment

Setting Max Concurrent Mailbox Moves on all CAS Servers

Have been working on a migration of a whole load of mailboxes. One of the first things I came across is the pre-defined limit on the number of concurrent mailbox moves that can be completed on a CAS server in an Exchange environment.

I knew that our environment could handle a few more moves than the limit of 2, particularly out of hours. I wanted to ensure that if anyone started any moves within business hours though that a limit was still applied.

So, I went and wrote this little script below. It goes through and updates all of the CAS servers in your Exchange environment and sets the value of the concurrent moves to whatever you specify. I then scheduled this to run each day at the end of the day to allow more scheduled moves overnight, and ran it again in the morning with the original limit of 2 moves just before we opened.

It uses Invoked Sessiosn too, so as long as remoting has been enabled on your CAS servers, then you don’t need Exchange installed on the box that you are going to run from.

# ----------------------------------------------------------------------------------------------------------
# PURPOSE:    Set the Max Concurrent Mailbox Moves on all CAS Servers
#
# VERSION     DATE         USER                DETAILS
# 1           24/06/2014   Craig Tolley        First version
#
#
# ----------------------------------------------------------------------------------------------------------
#Script Block to Run on Each Host
$ScriptBlock = {
    
    #Parameters
    $MaxMovesValue = 50
    #Start of Script
    Write-Output $("-"*50)
    Write-Output "Connected to: $(hostname)"
    #Get the Exchange Install Path, check it has been returned. Exit if not found.     
    [string]$ExInstallPath = $env:exchangeinstallpath 
    If ($ExInstallPath.Length -eq 0)
        {
        Write-Error "CAS Server $hostname did not return the Install Path."
        Exit
        }
    Else
        {
        Write-Output "Exchange is installed at: $ExInstallPath"
        }
        #Look up the location of the configuration file and check that it exists
    $ConfigFileName = $ExInstallPath + "bin\MSExchangeMailboxReplication.exe.config"
    If ((Test-Path $ConfigFileName) -eq $false)
        {
        Write-Error "Cannot find the MRS configuration file"
        Exit
        }
    #Create a backup of the configuration file. 
    Write-Output "Config Backup: $ConfigFileName.$(Get-Date -Format yyyyMMdd_HHmm).backup"
    Copy-Item $ConfigFileName -Destination ($ConfigFileName + "." + (Get-Date -Format yyyyMMdd_HHmm) + ".backup")
    #Configure the file with the specified values and save the changes. 
    Write-Output "Configuring XML File"
    [System.Xml.XmlDocument]$config = Get-Content ($ConfigFileName)
    $config.configuration.MRSConfiguration.MaxActiveMovesPerSourceMDB = [string]$MaxMovesValue
    $config.configuration.MRSConfiguration.MaxActiveMovesPerTargetMDB = [string]$MaxMovesValue
    $config.configuration.MRSConfiguration.MaxActiveMovesPerSourceServer = [string]$MaxMovesValue
    $config.configuration.MRSConfiguration.MaxActiveMovesPerTargetServer = [string]$MaxMovesValue
    $config.Save($ConfigFileName)
    
    #Restart the MRS to activate the changes. 
    Write-Output "Restarting Mailbox Replication Service"
    Get-Service MSExchangeMailboxReplication | Restart-Service -WhatIf
    
    Write-Output "Configuration Complete!"
    Write-Output $("-"*50)
    Write-Output ""
}
#Get all of the CAS Servers
$CAS = Get-ClientAccessServer
#Loop through all changing the setting. 
foreach($srv in $CAS)
    {
     Write-Output "Configuring CAS Server: $srv"
     Invoke-Command -ComputerName $srv -ScriptBlock $ScriptBlock
    }

Be the first to like.
Posted in Exchange, Powershell | Leave a comment

I’m back!

Now back in to doing some proper geeky stuff, which is what I love doing. So, here marks the start of a new set of posts which actually provide useful (at least in my opinion) tools, fixes, and scripts.

Kicking off with a little bit of Exchange, got a couple of NetApp PowerShell tools in the pipeline (no pun intended) and some VMware bits and pieces.

Be the first to like.
Posted in General Stuff | Leave a comment

Converting RTF-HTML – Not possible in a thread?

Been working on an application that needed to convert some RTF to HTML. Initially I thought this would be a reasonably simple requirement, and hoped that .Net might even include such a function to do this. Alas, I was being too optimistic.

So, trusting Google, ended up on this post from Matthew Manela – http://code.msdn.microsoft.com/Converting-between-RTF-and-aaa02a6e

Put that it, built the project, and ran on my UI thread, all works fine.

Put the same code in a background worker process or thread, and you will get an error: The calling thread must be STA, because many UI components require this.

The converter above unfortunately uses the built in RichTextBox component to perform a RTF-XAML conversion as the first step in the whole conversion process.

So, if anyone knows of a way that I can go from RTF-HTML in a separate thread from the UI, I would love to hear from you.

Be the first to like.
Posted in VB.net | Leave a comment

Ping Host with a Log File

Another potentially useful script here. It pings a host and records the output in a log file. The ping continues until you press Ctrl+C to stop it. This is useful for running in a background monitoring connectivity response times to a host and seeing if there is any network dropout.

Ping is no guarantee of performance, but it does give some idea if you have some underlying network problem over an extended period.

Run from the command line using the following format:

Ping_With_Log.vbs www.google.co.uk C:\GooglePing.txt
'Script to ping a host and record the output to a log file. 
'Craig Tolley
'Version 1.0 - 09/10/2013 - Initial Release
'Version 2.0 - 10/01/2014 - Complete rewrite to make significantly more usable and readable. 

'-------------------  START SCRIPT -------------------

'Check the provided arguments
If WScript.Arguments.Count = 2 Then
	HostToPing = WScript.Arguments(0)
	LogFileName = WScript.Arguments(1)
Else
	Wscript.Echo "Invalid number of arguments detected. " & Chr(13) & Chr(13) & _
				 "Usage: Ping_With_Log.vbs HostToPing LogFileName" & Chr(13) & Chr(13) & _
				 "The host to ping can either be an IP address or resolvable host name" & Chr(13) & Chr(13) & _
				 "The LogFile can either be just a filename, in which case it will be created in the current directory, or a full folder path and file name" & Chr(13) & Chr(13) & _
				 "e.g Ping_With_Log.vbs www.google.co.uk C:\GooglePing.txt"
	Wscript.Quit
End If

'Definitions
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set objShell   = CreateObject("Wscript.Shell")
Set objLogFile = objFSO.OpenTextFile(logfilename, 8, True)

'Run the command
strCommand = "%comspec% /c ping -t " & HostToPing
Set objExec   = objShell.Exec(strCommand)

Wscript.Echo "Ping with Error Log has started. To stop collecting Ping data press Ctrl+C"

'Keep writing the output of the application until Ctrl+C is pressed to cancel the ping
Do While objExec.StdOut.AtEndOfStream <> True
	objLogFile.WriteLine(Date & " " & Time & " " & objExec.StdOut.ReadLine)
Loop

'Open the log file for viewing
strCommand = "notepad.exe " & LogFileName
objShell.Exec(strCommand)

Be the first to like.
Posted in General Stuff | Leave a comment

Using Enum with ComboBox and setting Combobox value – VB.net

This is my take on setting a combobox to use an enum which has both a description and key value set. It also shows how this works when trying to assign a value to the combobox.


Enum MyLogEnum
Error = 1
Warning = 2
Information = 3
End Enum

ComboBox_LogLevel.DataSource = System.Enum.GetValues(GetType(MyLogEnum))

And to assign a numeric value to the Combobox and make it display correctly:

ComboBox_LogLevel.SelectedItem = DirectCast(SourceIntValue, MyLogEnum)

Unfortunately, I could not find an equally as elegant way of doing the same thing using a ComboBox that was part of a Datagrid View, so ended up rewriting the Enum as a Dictionary like this:


Dim LoggingLevelsList As New Dictionary(Of String, Integer)
For Each enumValue As Integer In [Enum].GetValues(GetType(MyLogEnum))
LoggingLevelsList.Add([Enum].GetName(GetType(MyLogEnum), enumValue), enumValue)
Next
DataGridCombo.DataSource = New Windows.Forms.BindingSource(LoggingLevelsList, Nothing)
DataGridCombo.DisplayMember = "Key"
DataGridCombo.ValueMember = "Value"

With this in the Datagridview Combobox, you do not need to do any casting from the source integer.

If someone has a better way for Enums on a DataGridView ComboBox, I would really like to hear them.

Be the first to like.
Posted in Programming, VB.net | Leave a comment
  • Tags

  • Categories

  • My LinkedIn Profile

    To see my LinkedIn profile, click here:

    Craig Tolley
  • September 2014
    M T W T F S S
    « Aug    
    1234567
    891011121314
    15161718192021
    22232425262728
    2930  
  • Meta

  • Top Liked Posts

    Powered by WP Likes

Swedish Greys - a WordPress theme from Nordic Themepark.