Visual Studio Code

For a long time I have been using PowerShell ISE for writing PowerShell scripts. But a few months ago I switched to Visual Studio Code.

The main reason for me was the built-in support for Git. This makes it so much easier for using Git. Specially if you just have started using Git. There is no need to remember commands for the Git command line or to use a separate Gui.

Another reason for me was that it not only supports PowerShell but also many other languages. Some have built-in support. For others you can install an extension. You can find many extension at the Visual Studio Code Marketplace. At my work we use Puppet for configuration management. And there is extension for Puppet. So I can use one editor for all my scripts.

Because Visual Studio Code supports extensions it is easy to customize the editor for you own needs. There are extension for themes, languages, keymaps, debuggers, linters etc.

To get started with VSCode I found the blog posts on the Hey Scripting Guy blog very helpful.

I you do not use Visual Studio Code I suggest you take a look at it. Ohhhhh, and it is free also!

Advertisements

Creating a zip file from a single file

In the last 2 posts I shared two PowerShell functions for extracting an archive and creating a new zip file from a folder. Today I will share a new function, New-ArchiveFromFile.

This function you can use when you want to compress a single file. This can be useful for log management. Instead of archiving log files in one big zip file I prefer archive log files in separate zip files. This way it is much easier and faster to remove older files that you don’t want to keep.

 

<#

.Synopsis

   Creates a new archive from a file

.DESCRIPTION

   Creates a new archive with the contents from a file. This function relies on the

   .NET Framework 4.5. On windwows Server 2012 R2 Core you can install it with

   Install-WindowsFeature Net-Framework-45-Core

.EXAMPLE

   New-ArchiveFromFile -Source c:\test\test.txt -Destination c:\test.zip

#>

function New-ArchiveFromFile

{

    [CmdletBinding()]

    [OutputType([int])]

    Param

    (

        # Param1 help description

        [Parameter(Mandatory=$true,

                   ValueFromPipelineByPropertyName=$false,

                   Position=0)]

        [string]

        $Source,

        # Param2 help description

        [Parameter(Mandatory=$true,

                   ValueFromPipelineByPropertyName=$false,

                   Position=1)]

        [string]

        $Destination

    )

    Begin

    {

        [System.Reflection.Assembly]::LoadWithPartialName(“System.IO.Compression.FileSystem”) | Out-Null

    }

    Process

    {

        try

        {

            Write-Verbose “Creating archive $Destination….”

            $zipEntry = $Source | Split-Path -Leaf

            $zipFile = [System.IO.Compression.ZipFile]::Open($Destination, ‘Update’)

            $compressionLevel = [System.IO.Compression.CompressionLevel]::Optimal

            [System.IO.Compression.ZipFileExtensions]::CreateEntryFromFile($zipfile,$Source,$zipEntry,$compressionLevel)

            Write-Verbose “Created archive $destination.”

        }

        catch [System.IO.DirectoryNotFoundException]

        {

            Write-Host “ERROR: The source $source does not exist!” -ForegroundColor Red

        }

        catch [System.IO.IOException]

        {

            Write-Host “ERROR: The file $Source is in use or $destination already exists!” -ForegroundColor Red

        }

        catch [System.UnauthorizedAccessException]

        {

            Write-Host “ERROR: You are not authorized to access the source or destination” -ForegroundColor Red

        }

    }

    End

    {

        $zipFile.Dispose()

    }

}


 

This is just a basic version. It could use some improvements.

Creating zip files in Windows Server Core

In my last post I talked about extracting zip files in Windows Server Core and shared a PowerShell function that I made.

Today I decided to make a new function that you can use to create a new archive. The function uses the same ZipFile class.

<#
.Synopsis
 Creates a new archive from a directory
.DESCRIPTION
 Creates a new archive with the contents from a folder. This function relies on the
 .NET Framework 4.5. On windwows Server 2012 R2 Core you can install it with 
 Install-WindowsFeature Net-Framework-45-Core
.EXAMPLE
 New-ArchiveFromDirectory -SourceDirectory c:\test -Destination c:\test.zip
#>
function New-ArchiveFromDirectory
{
 [CmdletBinding()]
 [OutputType([int])]
 Param
 (
 # Param1 help description
 [Parameter(Mandatory=$true,
 ValueFromPipelineByPropertyName=$false,
 Position=0)]
 [string]
 $SourceDirectory,

 # Param2 help description
 [Parameter(Mandatory=$true,
 ValueFromPipelineByPropertyName=$false,
 Position=1)]
 [string]
 $Destination
 )

 Begin
 {
 [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
 }
 Process
 {
 try
 {
 Write-Verbose "Creating archive $Destination...."
 [System.IO.Compression.ZipFile]::CreateFromDirectory($SourceDirectory, $destination)
 Write-Verbose "Created archive $destination."
 }
 catch [System.IO.DirectoryNotFoundException]
 {
 Write-Host "ERROR: The source directory $SourceDirectory does not exist!" -ForegroundColor Red
 }
 catch [System.IO.IOException]
 {
 Write-Host "ERROR: The destination $destination already exist!" -ForegroundColor Red
 }
 catch [System.UnauthorizedAccessException]
 {
 Write-Host "ERROR: You are not authorized to access the source or destination" -ForegroundColor Red
 }
 finally
 {

 }
 }
 End
 {

 }
}

Extracting zip files in Windows Server Core

For a project I wanted to use Windows Server 2012R2 Core as a stand alone server. Using Windows Server Core as a stand alone server can be challenging. Lot of things can be done with powershell or other command line tools.

When it is not possible to use file shares you have to find other ways to get files to the server. One method you can use is to download it from the internet. In powershell you can use the Invoke-WebRequest CmdLet.

Invoke-WebRequest -Uri http://someserver.tld/software.exe -OutFile .\downloads\software.exe

Another challenge is extracting archives like zip files. Windows Server Core and PowerShell don’t have native commands for extracting these files. So you need to download tools like 7Zip to do that.

Or you can make your own PowerShell script or function for it. I already had a function that creates archives but for extracting. So I thought I could change it to extract archives. In my script I used the com object shell.application. As I found out this is not working in Windows Server Core. The com object shell.application depends on explorer.exe. Which does not exist on Windows Server Core.

Thanks to Google I found out that .NET 4.5 has the System.IO.Compression namespace wich includes the ZipFile class. This class can be used for creating and extracting archives. And luckily you can use .NET classes in PowerShell. See the example below.

[System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
[System.IO.Compression.ZipFile]::ExtractToDirectory($ZipFile, $Destination)

Before you use it make sure that the .NET Framework 4.5 Core is installed.

Get-WindowsFeature -name NET-Framework-45-Core

If it is not installed you can install it with the following command.

Install-WindowsFeature -name NET-Framework-45-Core

To make it a bit more easy for myself I made a function for extracting archives.

<#
.Synopsis
 Expands compressed files
.DESCRIPTION
 Expands the contents of a compressed file to a folder. This function relies on the
 .NET Framework 4.5. On windwows Server 2012 R2 Core you can install it with 
 Install-WindowsFeature Net-Framework-45-Core
.EXAMPLE
 Expand-Archive -File c:\test.zip -Destination c:\test\
#>
function Expand-Archive
{
 [CmdletBinding()]
 [OutputType([int])]
 Param
 (
 # Param1 help description
 [Parameter(Mandatory=$true,
 ValueFromPipelineByPropertyName=$false,
 Position=0)]
 [string]
 $File,

 # Param2 help description
 [Parameter(Mandatory=$true,
 ValueFromPipelineByPropertyName=$false,
 Position=1)]
 [string]
 $Destination
 )

 Begin
 {
 [System.Reflection.Assembly]::LoadWithPartialName("System.IO.Compression.FileSystem") | Out-Null
 }
 Process
 {
 try
 {
 Write-Verbose "Extracting archive $file...."
 [System.IO.Compression.ZipFile]::ExtractToDirectory($file, $destination)
 Write-Verbose "Extracted archive $file to $destination."
 }
 catch [System.IO.FileNotFoundException]
 {
 Write-Host "ERROR: Archive $file does not exist!" -ForegroundColor Red
 }
 catch [System.IO.DirectoryNotFoundException]
 {
 Write-Host "ERROR: Destination $destination does not exist!" -ForegroundColor Red
 }
 catch [System.IO.PathTooLongException]
 {
 Write-Host "ERROR: The path specified for -File or -Destination is too long" -ForegroundColor Red
 }
 catch [System.UnauthorizedAccessException]
 {
 Write-Host "ERROR: You are not authorized to access the archive or destination" -ForegroundColor Red
 }
 finally
 {

 }
 }
 End
 {

 }
}

Let’s see what the next challenge will be.

Distribute files to servers with PowerShell

Our streaming platform has 10 edge servers but most of them are on standby. They will only be in service when there is a high demand. Most of the time they are shutdown.

At this point when there is a new costumer or we need to change a setting for an existing customer we have to do this by hand on every server. This is very time consuming of course and there is a risk of making a mistake on one of the edge servers.

The problem is that not all the edge servers are in the same network. So it was not possible to make a script that does a simple file copy. Of course all the servers have an internet connection. So I thought what if I put the configuration files on a web server. And let the edge servers download the files and put them in the right directory.  It sounds like something that could work.

But how do the edge servers know what files to download and where to put them. The last thing I wanted is to have to update the download script on every edge server when I have placed a new configuration file on the web server.

I solved this by placing an xml file on the web server, I called it manifest.xml. In the manifest file I did put the download url for the file and the destination directory on the edge server. The download script now first downloads the manifest.xml file. Parses the xml file and one by one gets the file from the url and places it in the  destination directory. So now I only have to make a change in one place.

You could schedule the script on the server. For example when the server starts.

The current version of the script is not very smart, but it is functional.

Script:

## Version: 0.2
## Date: 31-12-2012
#Change $VerbosePreference to Continue to see verbose messages
#Change $VerbosePreference to SilentlyContinue to hide verbose messages
$VerbosePreference = "Continue"

Function Get-FilesDistributionManifest {
 param ([string]$url=$(Throw "ERROR: URL is missing"))

$downloadstring = $url
 $webclient = new-Object System.Net.WebClient

try {
 Write-Verbose "Trying to download file $url"
 [xml]$manifest = $webclient.DownloadString($downloadstring)
 }
 catch [System.Net.WebException]{
 Write-Error "ERROR: Unable to download manifest $url. Check the url!"
 }
 finally {
 Write-Verbose "Succesfully downloaded manifest $url$filename."
 }
Return $manifest
}

Function Get-FilesDistributionFile {
 param ([ValidateNotNullorEmpty()][string]$url,
 [ValidateNotNullorEmpty()][string]$filename,
 [ValidateNotNullorEmpty()][string]$destination)
 $webclient = new-Object System.Net.WebClient

try {
 Write-Verbose "Trying to download file $url$filename"
 $webclient.DownloadFile("$url$filename","$destination$filename")
 }
 catch [System.Net.WebException] {
 Write-Error "ERROR: Unable to download $url$filename. Check the url!"
 }
 finally {
 Write-Verbose "Succesfully downloaded $url$filename to $destination$filename"
 }
}
$xml = Get-FilesDistributionManifest -url "http://webserver/manifest/manifest.xml"

foreach ($file in $xml.files.file) {
 Get-FilesDistributionFile -url $($file.url) -filename $($file.filename) -destination $($file.destination)
}

The manifest.xml file looks like this:

<?xml version="1.0" encoding="utf-8"?>
<files>
 <Version>1</Version>
 <file>
 <url>http://webserver/manifest/</url>
 <filename>file.txt</filename>
 <destination>c:\test\</destination>
 </file>
 <file>
 <url>http://webserver/manifest/</url>
 <filename>file2.txt</filename>
 <destination>c:\test\</destination>
 </file>
 <file>
 <url>http://webserver/manifest/</url>
 <filename>file3.txt</filename>
 <destination>c:\test\</destination>
 </file>
</files>

There are some improvements to make to the script.

  1. Authentication on the webserver.
  2. Only download the files when they are changed.
  3. Make a backup of the old file, if it exists, before downloading and replacing.
  4. Better error handling

Feel free to use the script in anyway you like!