VSS Writers are responsible for backups – if they stop working, then backups might fail. The solution to that is restarting VSS Writers, but the problem here is that there are many, and at least out of the box, there’s no mechanism to make this automatic… but there’s PowerShell.
I’m not interested in your code, I just want to restart these VSS Writers
In that case, head on to: mygithub, use the code and run:
Get-KPVSSWriter -Status Failed | Restart-KPVssWriter
That function will go ahead, retrieve all failed VSS Writers, and restart associated services with them. If that won’t help, then you probably need to reboot the server.
Let’s talk PowerShell
The source code is available on GitHub.
As far as VSS Writers go, there were a few issues needed to be addressed for the function:
- the VssAdmin.exe tool is CMD only, and returns only strings.
- There’s no filter in VssAdmin, thus it always returns all writers
- The writers are associated with many Windows Services, and it’s not always obvious which service is responsible for given VSS Writer
Therefore I’ve written a couple of functions:
- Get-KPVssWriter – that function will convert the output string from VssAdmin into a PowerShell objects + it can filter results by state (at the moment Stable or Failed).
- Restart-KPVssWriter – this function takes the name of the VSS writer and restarts required service with it. It has hard-coded names of VSS writers with associated services.
How do you make a use of these?
You pipe results from Get function, to Restart function thus:
Get-KPVSSWriter -Status Failed | Restart-KPVssWriter
will retrieve all failed writers and restart them. Let’s look into actual code.
Get-KPVssWriter
First is to run VSSAdmin and start converting the output into PowerShell object:
Select-String
VSSAdmin list writers |
Select-String -Pattern 'Writer name:' -Context 0, 4
These two lines will convert the output from vssadmin into PowerShell object. The Select-String really shines here as:
- -Pattern parameter allows it to search for a specific phrase. In that case I’m looking for ‘Writer name:’ as each listed writer starts with that line.
- -Context 0,4 tells PowerShell that for each pattern (line) you found, match 0 line prior to it, and 4 past it.
Thus if we look for this sample:
Writer name: 'SqlServerWriter' Writer Id: {a65faa63-5ea8-4ebc-9dbd-a0c4db26912a} Writer Instance Id: {9a2c071c-f231-4a9d-abb3-0b15e1866b79} State: [1] Stable Last error: No error Writer name: 'ASR Writer' Writer Id: {be000cbe-11fe-4426-9c58-531aa6355fc4} Writer Instance Id: {1dcb9866-6b86-4a5f-8dcc-eaf270bbfb2b} State: [1] Stable Last error: No error Writer name: 'MSSearch Service Writer' Writer Id: {cd3f2362-8bef-46c7-9181-d62844cdc0b2} Writer Instance Id: {b4a2be5b-71d4-4579-871c-94b725607c33} State: [1] Stable Last error: No error
You can see that there’s pattern: Each Writer Name is followed by four line of text, and then the next one appears.
Select-String will then create an object for each pattern found, with properties of Line, Context.PostContext and Context.PreContext.
And since we know that:
Line = pattern found,
Context.PostContext = an array of each line found past pattern
Context.PreContext. = an array of each line found prior the pattern
It’s very easy to create the object, but first we need to remove the leftovers of the string.
Replace
ForEach-Object { #Removing clutter Write-Verbose "Removing clutter " $Name = $_.Line -replace "^(.*?): " -replace "'" $Id = $_.Context.PostContext -replace "^(.*?): " $InstanceId = $_.Context.PostContext -replace "^(.*?): " $State = $_.Context.PostContext -replace "^(.*?): " $LastError = $_.Context.PostContext -replace "^(.*?): "
For every object generated by Select-String, I will assign a specific variable, and run -replace to remove unnecessary characters. Replace can also take regex , making it super effective while working with text.
PsCustomObject
The final step is to assembly all variables created in previous step into a PowerShell object:
foreach ($Prop in $_) { $Obj = [pscustomobject]@{ Name = $Name Id = $Id InstanceId = $InstanceId State = $State LastError = $LastError } }#foreach
Since the main purpose of using this function is to restart failed, I thought it would be worth to add a parameter for filtering out VSS Writers based on status, thus this simple if statement which amends what will be outputed by the function:
If ($PSBoundParameters.ContainsKey('Status')) {
Write-Verbose "Filtering out the results"
$Obj | Where-Object { $_.State -like "*$Status" }
} #if
else {
$Obj
} #else
There we go, the analogue application has been converted into a digital form (LOL), we can go ahead now and work on the actual restarts.
Restart-KPVssWriter
This function will take the name of the VSS writer that requires restart, match Windows service to it, and restart the service.
Switch
PowerShell has a beatifull command – Switch – that eliminates the need of writing complex if statements. Let’s have a look:
Switch ($Name) {
'ASR Writer' { $Service = 'VSS' }
'BITS Writer' { $Service = 'BITS' }
'Certificate Authority' { $Service = 'EventSystem' }
'COM+ REGDB Writer' { $Service = 'VSS' }
'DFS Replication service writer' { $Service = 'DFSR' }
'DHCP Jet Writer' { $Service = 'DHCPServer' }
'FRS Writer' { $Service = 'NtFrs' }
'FSRM writer' { $Service = 'srmsvc' }
'IIS Config Writer' { $Service = 'AppHostSvc' }
'IIS Metabase Writer' { $Service = 'IISADMIN' }
'Microsoft Exchange Writer' { $Service = 'MSExchangeIS' }
'Microsoft Hyper-V VSS Writer' { $Service = 'vmms' }
'NTDS' { $Service = 'NTDS' }
'OSearch VSS Writer' { $Service = 'OSearch' }
'OSearch14 VSS Writer' { $Service = 'OSearch14' }
'Registry Writer' { $Service = 'VSS' }
'Shadow Copy Optimization Writer' { $Service = 'VSS' }
'SPSearch VSS Writer' { $Service = 'SPSearch' }
'SPSearch4 VSS Writer' { $Service = 'SPSearch4' }
'SqlServerWriter' { $Service = 'SQLWriter' }
'System Writer' { $Service = 'CryptSvc' }
'TermServLicensing' { $Service = 'TermServLicensing' }
'WINS Jet Writer' { $Service = 'WINS' }
'WMI Writer' { $Service = 'Winmgmt' }
default {$Null = $Service}
} #Switch
Hell yeah, that would be quite a if statement. Instead of that, switch will match the name on the left, and execute the code to the right.
The last line is interesting, default – if switch won’t match the name, it will execute that one instead. I’m casting that into $null.
If
The final step is to restart service:
IF ($Service) {
Write-Verbose "Found matching service"
$S = Get-Service -Name $Service
Write-Host "Restarting service $(($S).DisplayName)"
$S | Restart-Service -Force
}
ELSE {
Write-Warning "No service associated with VSS Writer: $Name"
}
Simple restart of service if it was matches earlier on, or just write warning if it wasn’t found.
Happy ever after
I hope you found this useful. What can be done here is e.g. put the script into a scheduled task to run hourly, and hopefully you will have one problem less to worry about – as nobody likes to see failed backups on Monday morning.