Backup Hyper-V VMs with PowerShell

Backing up your Hyper-V VM environment using PowerShell has never been easier! PowerShell allows you to backup your VMs, log the results and powershell-tips-and-trickseven email you a report with a few easy commands.

In this case, I have two physical hosts, VH-SERVER01 and VH-SERVER02. Each of them has quite a bit of HD space so for the purposes of recovery from a simple hardware failure, back up the VMs on one host to the other is a great, simple way to go.

I created shares on each server for the other to use as a repository. VH-SERVER0xexports. I map the Z: drive to this share for simplicity’s sake.

One caveat that I found is that a Domain Controller VM will not export to a SMB share, even when it is mapped with a drive letter. I researched this and found all sorts of suggestions about permissions related to compluter accounts and despite my efforts, I was unable to resolve the direct export. So, for the DCs, I export them locally and then move the files to the same share where the other exports are sent.

After all the exports are done, I call the script Get-DirStats.ps1 to calculate the sizes of the exported files. I output the results to a file. The script code for that is located below the main script. I then use the great command, Send-MailMessage to email me the outputted results! Note that you need to have an SMTP server that will allow you to route email. Since this is a corporate client, I have an Exchange Receive Connector that will allow me to send SMTP traffic without authentication.

Some helpful links:

technet.microsoft.com/en-us/library/hh848491.aspx

social.technet.microsoft.com/Forums/scriptcenter/en-US/11213a59-e84b-47da-a505-dd6c59ce18de/powershell-troubles-with-bulk-moveitem-script?forum=ITCG

technet.microsoft.com/en-us/library/hh849925.aspx

stackoverflow.com/questions/25917637/create-folder-with-current-date-as-name-in-powershell

blogs.technet.microsoft.com/heyscriptingguy/2012/05/25/getting-directory-sizes-in-powershell/

The script code!

#deletes and adds Z: as a persistent drive since this script needs to run whether someone is logged in or not

Get-PSDrive Z | Remove-PSDrive
New-PSDrive -Name “Z” -PSProvider “FileSystem” -Root “\VH-SERVER01exports” -Persist
Remove-Item D:ExpADSERVER02 -Recurse
New-Item -ItemType directory -Path “D:ExpADSERVER02”

#define export paths

$ExportPath_D = “D:ExpADSERVER02”
$ExportPath_Z = “Z:”
$date = Get-Date
$date = $date.ToString(“yyyy-MM-dd”)

#Deletes files-folders that are older than 20 days

$limit = (Get-Date).AddDays(-20)
$path = $ExportPath_Z

# Delete files older than the $limit.

Get-ChildItem -Path $path -Recurse -Force | Where-Object { !$_.PSIsContainer -and $_.CreationTime -lt $limit } | Remove-Item -Force

# Delete any empty directories left behind after deleting the old files.

Get-ChildItem -Path $path -Recurse -Force | Where-Object { $_.PSIsContainer -and (Get-ChildItem -Path $_.FullName -Recurse -Force | Where-Object { !$_.PSIsContainer }) -eq $null } | Remove-Item -Force -Recurse

New-Item -ItemType directory -Path “$ExportPath_Z$date”

#Exports are here

Export-VM -Name “ADSERVER02” -Path $ExportPath_D

Move-item $($ExportPath_D) Z:$($date)

Export-VM -Name “SERVER03″,”SERVER04″,”SERVER05″,”SERVER06” -Path $ExportPath_Z$date

#Report creation and email

c:scriptsGet-DirStats.ps1 -Path Z:$($date) -Every >C:Backup-LogsVH-SERVER02-$date.csv

Send-MailMessage -From “Backups <backups@YOURDOMAIN.com>” -To “Alerts <alerts@YOURDOMAIN.com>” -Subject “VH-SERVER02 Backup Status for $date” -Body “This is the VM backup report for VH-SERVER02.” -Attachments “c:backup-logsVH-SERVER02-$date.csv” -Priority High -dno onSuccess, onFailure -SmtpServer “MAIL.YOURDOMAIN.COM”

exit

Get-DirStats.ps1
<#
.SYNOPSIS
Outputs file system directory statistics..DESCRIPTION
Outputs file system directory statistics (number of files and the sum of all file sizes) for one or more directories.

.PARAMETER Path
Specifies a path to one or more file system directories. Wildcards are not permitted. The default path is the current directory (.).

.PARAMETER LiteralPath
Specifies a path to one or more file system directories. Unlike Path, the value of LiteralPath is used exactly as it is typed.

.PARAMETER Only
Outputs statistics for a directory but not any of its subdirectories.

.PARAMETER Every
Outputs statistics for every directory in the specified path instead of only the first level of directories.

.PARAMETER FormatNumbers
Formats numbers in the output object to include thousands separators.

.PARAMETER Total
Outputs a summary object after all other output that sums all statistics.
#>

[CmdletBinding(DefaultParameterSetName=”Path”)]
param(
[parameter(Position=0,Mandatory=$false,ParameterSetName=”Path”,ValueFromPipeline=$true)]
$Path=(get-location).Path,
[parameter(Position=0,Mandatory=$true,ParameterSetName=”LiteralPath”)]
[String[]] $LiteralPath,
[Switch] $Only,
[Switch] $Every,
[Switch] $FormatNumbers,
[Switch] $Total
)

begin {
$ParamSetName = $PSCmdlet.ParameterSetName
if ( $ParamSetName -eq “Path” ) {
$PipelineInput = ( -not $PSBoundParameters.ContainsKey(“Path”) ) -and ( -not $Path )
}
elseif ( $ParamSetName -eq “LiteralPath” ) {
$PipelineInput = $false
}

# Script-level variables used with -Total.
[UInt64] $script:totalcount = 0
[UInt64] $script:totalbytes = 0

# Returns a [System.IO.DirectoryInfo] object if it exists.
function Get-Directory {
param( $item )

if ( $ParamSetName -eq “Path” ) {
if ( Test-Path -Path $item -PathType Container ) {
$item = Get-Item -Path $item -Force
}
}
elseif ( $ParamSetName -eq “LiteralPath” ) {
if ( Test-Path -LiteralPath $item -PathType Container ) {
$item = Get-Item -LiteralPath $item -Force
}
}
if ( $item -and ($item -is [System.IO.DirectoryInfo]) ) {
return $item
}
}

# Filter that outputs the custom object with formatted numbers.
function Format-Output {
process {
$_ | Select-Object Path,
@{Name=”Files”; Expression={“{0:N0}” -f $_.Files}},
@{Name=”Size”; Expression={“{0:N0}” -f $_.Size}}
}
}

# Outputs directory statistics for the specified directory. With -recurse,
# the function includes files in all subdirectories of the specified
# directory. With -format, numbers in the output objects are formatted with
# the Format-Output filter.
function Get-DirectoryStats {
param( $directory, $recurse, $format )

Write-Progress -Activity “Get-DirStats.ps1” -Status “Reading ‘$($directory.FullName)'”
$files = $directory | Get-ChildItem -Force -Recurse:$recurse | Where-Object { -not $_.PSIsContainer }
if ( $files ) {
Write-Progress -Activity “Get-DirStats.ps1” -Status “Calculating ‘$($directory.FullName)'”
$output = $files | Measure-Object -Sum -Property Length | Select-Object `
@{Name=”Path”; Expression={$directory.FullName}},
@{Name=”Files”; Expression={$_.Count; $script:totalcount += $_.Count}},
@{Name=”Size”; Expression={$_.Sum; $script:totalbytes += $_.Sum}}
}
else {
$output = “” | Select-Object `
@{Name=”Path”; Expression={$directory.FullName}},
@{Name=”Files”; Expression={0}},
@{Name=”Size”; Expression={0}}
}
if ( -not $format ) { $output } else { $output | Format-Output }
}
}

process {
# Get the item to process, no matter whether the input comes from the
# pipeline or not.
if ( $PipelineInput ) {
$item = $_
}
else {
if ( $ParamSetName -eq “Path” ) {
$item = $Path
}
elseif ( $ParamSetName -eq “LiteralPath” ) {
$item = $LiteralPath
}
}

# Write an error if the item is not a directory in the file system.
$directory = Get-Directory -item $item
if ( -not $directory ) {
Write-Error -Message “Path ‘$item’ is not a directory in the file system.” -Category InvalidType
return
}

# Get the statistics for the first-level directory.
Get-DirectoryStats -directory $directory -recurse:$false -format:$FormatNumbers
# -Only means no further processing past the first-level directory.
if ( $Only ) { return }

# Get the subdirectories of the first-level directory and get the statistics
# for each of them.
$directory | Get-ChildItem -Force -Recurse:$Every |
Where-Object { $_.PSIsContainer } | ForEach-Object {
Get-DirectoryStats -directory $_ -recurse:(-not $Every) -format:$FormatNumbers
}
}

end {
# If -Total specified, output summary object.
if ( $Total ) {
$output = “” | Select-Object `
@{Name=”Path”; Expression={“<Total>”}},
@{Name=”Files”; Expression={$script:totalcount}},
@{Name=”Size”; Expression={$script:totalbytes}}
if ( -not $FormatNumbers ) { $output } else { $output | Format-Output }
}
}

Bulk Changes to Active Directory

I recently had to change 350 contact objects in a client’s Active Directory. Normally, I would have just used powershell for this sort of thinAD Modifyg. But the technet article about the change to be made also mentioned a tool that I had heard of but never used: AD Modify.net. I decided to try it out.

It’s really a GUI of sorts that will allow you to make bulk changes to AD objects from an easy to use GUI. Just connect to the AD, choose your container and the objects within, and away you can go!

admod

In this case, I had to remove the secondary SMTP address from hundreds of contacts.

Using the easy to navigate GUI, I added all the contact objects to be altered. I was then presented with an interface very similar to that of ADUC. I added the parameters that I needed and made the changes in no time flat.

If you need the functionality of PowerShell but are intimidated by the syntax, give this nifty utility a try!

https://admodify.codeplex.com/

 

The (almost) free web site – and learn Git too!

This is not a Drupal post. But I do have to share this anyway. I needed a site for the consulting side of my business but I don’t want to shell out much cash. OK, no cash at all.

So, i went digging and found that Github allows you to post a small site for free. No strings, no garbage. No database either so you can’t have a Drupal site. This isn’t the only site that will let you do that but it is one of the only ones that will also let you have your own domain name too. So, I spent two hot US quarters (50 cents) and bought as domain name for a year. I then used that name to establish my new site Lande Tech.

The only piece of the puzzle missing is DNS. Enter freedns.afraid.org, a free service that will allow your domain’s dns needs to be hosted for free.

You won’t get your new domain’s email out of this (although afraid.org will host your mx record) but you will get your own site and domain name for SUPER cheap!

Plus, you have the added benefit of using Git for the transfer of your data to Github Pages. Git was developed by Linus himself and is more or less the standard VCS (Version Control System) for Open Source development. I talked about Pantheon earlier – They use Git for their platform. If you do Open Source, you need to be familiar with Git.

Try out Pantheon for free Drupal hosting

There are a lot of good Drupal hosting sites out there. But Pantheon stands out because it offers FREE hosting. Are there caveats? sure, but that is to be expected. The two biggest are ads on your site and a domain name under their root.

Right now, I have a client who is sitting on the fence about a new site. I have a somewhat vested interest in his success so my plan is to develop a site first and then sell it to him. Now this is a weird use case sure. But it wouldn’t be possible without Pantheon’s offer of free hosting. And my client can choose a payment plan down the road with his own URL and no ads once he commits.

Pantheon offers some really cool features too. Either SFTP or Git access, Drupal distributions and a really easy install requiring nothing more than some mouse clicks.

They also offer a cool multistage dev/test/live environment that really allows you to develop safely in a multideveloper environment. Add Git to the equation and you have a distributed local environment with version control.

The first site that I am doing is called Landetech. So the urls from Pantheon are:

dev-landetech.gotpantheon.com

test-landetech.gotpantheon.com

live-landetech.gotpantheon.com

With the Pantheon control panel, you have clear and concise control over changes committed to each environment. Very cool stuff.

Pantheon also uses Varnish for caching for greatly improved performance, something that Drupal usually needs. Check them out.

https://www.getpantheon.com/

Developing your site

In case you haven’t figured it out yet, Drupal is best if you learn ALL the ins and outs of the product. and that means learning how to:

  • Create subthemes from existing themes
  • Customize modules as needed and sharing with the community
  • Customize CSS (Very important)
  • Theme functions
  • Become a member of Drupal.org for the ridiculously low price of 30 bucks.
  • Get involved with the community
  • Probably a lot more

Here is a great site that will help you get started with some of the more serious development work that can be done in Drupal. With Drupal, it seems to me that knowing where the HTML that you see in Firebug is generated is an enormous part of making the platform work for you. So check this site out.

I also recommend trying out Aptana Code Editor.

Firebug for Firefox

Chrome’s Element Inspector

How to find out what font you are looking at.

Chrome has a great extension for that allows you to click on any web page’s text and see details about the font that is being used. very cool. similar to the tool that allows you to see what color you are looking at. very nice. free from the extensions in the Chrome store.

WhatFont 2.0.2

The easiest way to identify fonts on web pages.

Image

One small CSS change to my Drupal Home Page

start learning drupal - docresource.org

I want to move the whole page down just a little bit. My logo is flush against the top of the page. I just want a little head room. So, I want to do this via CSS.

Using Firebug and Firefox, i right click on the top of the page and choose inspect element. this doesn’t get me right where i need to be but it is close.

the two ss will show you everything that you need to see in red outlines. by using firebug, i can trace down the element I want by simply digging a bit into the

lower left toolbar of firebug. Then, on the right, i see the margin area that I need to change. I can make the change in FB in a completely safe way to make sure that the change is the one that I want. the right hand side of FB also shows me the CSS file that it is coming from and even the line number that I need to alter.

start learning drupal - docresource.org

I can then FTP the CSS file over SSH, open it with Aptana (or textpad, if you like) and change it. SSH will sense the changed file and ask me if I want to overwrite it. I do and then the change is a part of prod. Very easy, very safe and very nice!

 

 

 

 

 

Microsoft Translator Widget integrates with Drupal

This is so cool. I found what I have been looking for. The MS Translator Widget. It is free and can easily be integrated into a Drupal site via the Widgets module. This isn’t quite how I am going to use it but here are before and after screen shots from my front page. The first is the site as a spanish speaker would see it after login. You can see that it is already in spanish except for the content in the middle. these are the RSS feed stories that come in all the time. At the top right you can see the widget for the MS translator. I was able to get this up and running in no time at all using the widgets module.

I’ll post tomorrow how i configed the widgets module.

Image

This is what it look like after I translate the page via the widget. All of the spanish text is left alone (except for the language drop down “Languages”… not sure will have to check but I think I know…) but the english teasers of the RSS stories are now translated. My spanish is good enough for me to be able to tell that the translation is pretty good. and free. There are some additional options for the widget that include auto translation, transparent to the user, and others. auto translation is generally not embraced because machine translation is generally about 70% correct. But, by translating the interface through the Drupal tools and using this widget, I believe that I can deliver a true multilingual site. and that is very cool.

Image

Multilingual update – Microsoft Translate widget

I am basically done translating the interface into spanish from english. and it really was much easier than I expected. i used babelfish.com to help me with the translations that i had to do manually. the user can get spanish from the start if the account is setup that way or they can choose spanish or english from the language switcher drop down.

So that takes care of the interface. and that is a huge deal. but I really want to provide a relatively easy way to translate the huge amount of content that the site warehouses. So I am looking at The MS Translator Widget. It is a free javascript widget that should be easy to integrate with your site. When I figure out how to do it I will post the solution here.