Skip to content

Author: Stefan van Bruggen

[Server 2016] 70-740 exam (MCSA 2016)

So, after endless delays and procrastination I finally started the path to getting my MCSA certification. (I know, about time after working in IT for almost 9 years..)

Today, I passed the new 70-740 Installation, Storage, and Compute with Windows Server 2016 exam!

It wasn’t easy, the exams for MCSA 2016 just got out of beta so there is an extreme lack of study material available. If you are planning to take this exam soon, I can recommend using the following resources:

  • Exam Ref 70-740 Installation, Storage and Compute with Windows Server 2016 by Craig Zacker (I used the eBook)
  • Pluralsight video courses by Greg Shields

And of course some hands-on experience if possible.

The exam itself has a lot of focus on Hyper-V configuration and Failover Clustering, in my case about 75% of the questions were about these subjects.

All in all, I appreciated that the exam focused on plausible scenario’s instead of knowing dry facts and PowerShell commands. Do not take this exam lightly though, because it is definitely not easy.

[Veeam] Manually remove restore points

Veeam does not have a built-in function to remove restore points manually, it took me a while but after trying a lot of different ways and scripts I have found a way to do it. (Please note that this is a last resort, Veeam should clean-up old restore points by itself)

  1. Go to Backup & Replication -> Backups.
  2. Right-click the job you want to edit and click ‘Remove from configuration’ (Do not delete from disk!).
  3. Open the Windows Explorer and browse to the job’s folder in the backup repository.
  4. Delete the restore points you want to remove, and delete the .VBM file.
  5. Re-import the most recent .VBK file in the Veeam.
  6. Run the following script using the Veeam Powershell to generate a new .VBM file:
    #[Veeam.Backup.Core.CCredentilasStroreInitializer]::InitLocal() to work with Lin repository
    
    $backupName = Read-Host "Enter backup name for meta regeneration"
    
    Add-Type -Path "C:\Program Files\Veeam\Backup and Replication\Backup\Veeam.Backup.Core.dll"
    [Veeam.Backup.Common.LogFactory]::InitializeConsoleLog()
    
    Try
    {
    $backup = [Veeam.Backup.Core.CBackup]::GetAll() | Where { $_.Name.Equals($backupName) } | Select -index 0
    
        if ($backup -eq $null)
          {
            throw ("There is no backup with specified name : " + $backupName)
          }
    
    $storageAccessor = [Veeam.Backup.Core.CStorageAccessorFactory]::Create($backup)
    
    [Veeam.Backup.Core.CCredentilasStroreInitializer]::InitLocal()
    
    
    
    $metaUpdater = New-Object Veeam.Backup.Core.CSimpleBackupMetaUpdater
    $metaEx = new-object Veeam.Backup.Core.CBackupMetaEx -ArgumentList $backup, $storageAccessor, $metaUpdater
    $metaEx.GenerateAndSave()
    
    $metaFilePath = $backup.GetMetaFilePath($storageAccessor.FileCommander)
    Write-Host "Successfully saved meta for backup $($backupName) on $($storageAccessor.Name) : $($metaFilePath)"
    }
    Catch
    {
    Write-Host $_.Exception.Message
    Write-Host $_.Exception.StackTrace
    Write-Host $_.Exception.InnerException.Message
    Write-Host $_.Exception.InnerException.StackTrace
    Break
    }
    
    $Reader = [System.IO.StreamReader] $metaFilePath 
    $MyString = $Reader.ReadToEnd();
    $Reader.Dispose();
    
    
    $removeString = 'LinkId="00000000-0000-0000-0000-000000000000"';
    $index = $MyString.IndexOf($removeString);
    $lengh = $removeString.Length;
    
    $start = $MyString.Substring(0, $index);
    $end  = $MyString.Substring($index + $lengh);
    
    $clean = $start + $end;
    
    
    
    $writer = [System.IO.StreamWriter] $metaFilePath
    $writer.WriteLine($clean);
    $writer.Close();
  7. Remove the imported backup from Veeam
  8. Re-scan the backup repository (Backup Infrastructure -> Backup Repositories)
  9. Go to the associated backup job and re-map the backup. You can do this by editing the job, going to the Storage-tab and click Map backup.

And that’s it! Now you’ve reclaimed the disk space you needed, removed corrupted backups, or whatever reason you had for removing the restore points.

[Certifications] Nutanix Platform Professional

There we go! Nailed the NPP exam last friday.

The exam itself was pretty good, most questions were relevant to real life situations instead of the usual stuff like “Our product is the best, please mark the answer that says we are the best”.

The only downside is that the training for this exam is not nearly enough, you definitely need some experience working with Nutanix to be able to achieve a high enough score to get the certification.

Anyway, onwards to the next couple of certs: Windows Server 2016!

[Veeam] Repeatedly failing replica-jobs, fixed!

So, let’s take a break from all the Powershell creativity and take a look at everybody’s favourite thing in IT: Backups! Failed backups!

(The screenshots are unclear and censored to protect customer information)

The problem here is that Veeam’s replication jobs started failing, stating that an ‘Invalid Snapshot Configuration’ was the problem. Sounds easy, right?
Well, it turns out that this can cause a lot of work to get this fixed, so to save you some time I documented the solution for you.

First I tried consolidation the snapshots, but it greeted me with the following error:

image004

A CID mismatch.. not on my watch! Let’s check this out and start an SSH connection to the ESX-host where these VMs are placed and run some checks:
image008

So, for some reason the snapshots have registered themselves as the parent CID instead of the actual base disk:
unnamed

We can fix this! Open the .vmdk-files using VIM and simply edit the parent CID to the CID of the base disk.

Now consolidate the snapshots again, restart your replica jobs, problem solved!

[Powershell] Corrupt Userprofiles – Quick fix via Powershell

Customer X had a long ongoing problem with userprofiles getting corrupted due to their antivirus solution holding the ntuser.dat file hostage. It took a while before we found the cause of this problem so we had to think of a quick fix to keep things running.
Another problem was that the locally stored corrupted profile was getting synced to the profile server, causing trouble for users on multiple workstations.

To save time and to give the sysadmins an easy way to clean these corrupted profiles, I automated the process with this (admittedly messy) script.

I’ve also added some workarounds that start the required services used in this script, because in some cases these are not enabled (WinRM, Remote Registry, etc.)

Note: The text in the popup is in Dutch.

<#
.Description
   Script for cleaning up corrupted userprofiles.
   Gebaseerd op: 	https://technet.microsoft.com/en-us/library/ff730941.aspx
					https://msexchange.me/2014/03/11/powershell-custom-gui-input-box-for-passing-values-to-variables/

   Current Version: 1.2
   Versiebeheer:
   ----------------
   v1.0: First working version
   v1.1: Added automated starting of the required services
   v1.2: Script cleaned up
   
   Door: Stefan van Bruggen, de Koning B.V.
		 info@svanbruggen.nl

#>

function button ($title,$username,$workstation) {

## Laden van assemblys
[void][System.Reflection.Assembly]::LoadWithPartialName( “System.Windows.Forms”)
[void][System.Reflection.Assembly]::LoadWithPartialName( “Microsoft.VisualBasic”)

## Formaat van het input-venster
$form = New-Object “System.Windows.Forms.Form”;
$form.Width = 500;
$form.Height = 200;
$form.Text = $title;
$form.StartPosition = [System.Windows.Forms.FormStartPosition]::CenterScreen;

## Definieren van label1
$textLabel1 = New-Object “System.Windows.Forms.Label”;
$textLabel1.Left = 25;
$textLabel1.Top = 15;
$textLabel1.Text = $username;

## Definieren van label2
$textLabel2 = New-Object “System.Windows.Forms.Label”;
$textLabel2.Left = 25;
$textLabel2.Top = 50;
$textLabel2.Text = $workstation;


## Definieren van input-veld box1
$textBox1 = New-Object “System.Windows.Forms.TextBox”;
$textBox1.Left = 150;
$textBox1.Top = 10;
$textBox1.width = 200;

## Definieren van input-veld box2
$textBox2 = New-Object “System.Windows.Forms.TextBox”;
$textBox2.Left = 150;
$textBox2.Top = 50;
$textBox2.width = 200;

## Definieren van standaardwaarde input-velden
$textBox1.Text = "Gebruikersnaam";
$textBox2.Text = "Werkstation";

## Definieren van OK-knop
$button = New-Object “System.Windows.Forms.Button”;
$button.Left = 360;
$button.Top = 85;
$button.Width = 100;
$button.Text = “Ok”;

## Definieren van actie bij klikken OK
$eventHandler = [System.EventHandler]{
$textBox1.Text;
$textBox2.Text;
$form.Close();};
$button.Add_Click($eventHandler) ;

## Definieren van controls
$form.Controls.Add($button);
$form.Controls.Add($textLabel1);
$form.Controls.Add($textLabel2);
$form.Controls.Add($textBox1);
$form.Controls.Add($textBox2);
$ret = $form.ShowDialog();

## Definieren van ingevoerde waarden

return $textBox1.Text, $textBox2.Text
}

$return= button “Invoer Gebruikersgegevens” “Gebruikersnaam” “Computernaam”

## De waarden die zijn ingevoerd zijn op te vragen via
## $return[0] voor de username
## $return[1] voor het werkstation

## Define profile share
$ProfileShare = "\\SERVER01.domain.local\Profiles$\"

## Define full path to userprofile
$FullPath = $ProfileShare + $return[0] + ".v2" 

## Rename ntuser.dat on the profile server
Get-ChildItem $FullPath -recurse -force | Where {$_.name -eq "ntuser.dat"} | rename-item -newname {  $_.name  -replace "ntuser.dat","ntuser.dat_old"  }

## Get the SID for the deletion of the registry key
$usersidrequest = $return[0]
$userSID = ([System.Security.Principal.NTAccount]("ahoy.local\$usersidrequest")).Translate([System.Security.Principal.SecurityIdentifier]).Value

## Delete registry key on the werkstation and starting of Remote Registry service
$workstation = $return[1]
(Get-Service -ComputerName $workstation -Name "WinRM").Start()
(Get-Service -ComputerName $workstation -Name "RemoteRegistry").Start()

$reg = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey(‘LocalMachine’,$workstation)

       $regKey= $reg.OpenSubKey(“SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList”,$true )
       $regKey.DeleteSubKeyTree($userSID)

## Rename locally cached userprofile to _old
$workstationunc = "\\" + $workstation + "\c$\Users\"
$workstationuser = $return[0]
$rename = $workstationuser + "_old"
Get-ChildItem $workstationunc -force | Where {$_.name -eq $workstationuser} | rename-item -newname {  $_.name  -replace $workstationuser,$rename } -ErrorAction Continue

 

 

[WSUS] Cleaning up superseded updates

Sadly, the WSUS cleanup wizard neglects to clean up updates that were approved in the past but have been superseded since.

Because these updates tend to use a lot of diskspace, I use a short Powershell script that checks all updates for superseded updates and declines them. After this, the WSUS cleanup wizard can be run again to clear up diskspace.

################################################################
#                                                              #
#  Script for cleaning up superseded updates in WSUS.          #
#                                                              #
#                                                              #
#  -Stefan van Bruggen                                         #
#                                                              #
################################################################

# Change the WSUSserver variable to your server hostname

[String]$WSUSserver = "SERVERNAAM"
[Int32]$port =8530
[Boolean]$SecureConnection = $False

# .NET assembly WSUS inladen

[void][reflection.assembly]::LoadWithPartialName("Microsoft.UpdateServices.Administration")

# Variable used for counting the total number of declined updates
$count = 0

# Connecting to server

$updateServer = [Microsoft.UpdateServices.Administration.AdminProxy]::getUpdateServer($WSUSserver,$SecureConnection,$port)

write-host "Connected to WSUS Server" -foregroundcolor "green"

$updatescope = New-Object Microsoft.UpdateServices.Administration.UpdateScope

# Get the updates and check for superseded updates
$u=$updateServer.GetUpdates($updatescope)

foreach ($u1 in $u )
{
if ($u1.IsSuperseded -eq 'True')
# Declining superseded updates
{
write-host Declined Update : $u1.Title
$u1.Decline()
$count=$count + 1
}
}
write-host Total Declined Updates: $count

exit
Stefan van Bruggen - 2019