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

100 Days of DevOps with PowerShell

Just found a series of very interesting articles. http://www.systemcentercentral.com/100DaysOfDevOps/

The people from systemcentercentral.com are doing a series of 100 articles about DevOps with PowerShell. If you want to learn more about I recommend to read the articles. At the moment they are halfway.

 

Add hundreds of hostheaders to one website with PowerShell

At the moment I am busy with replacing an old server with a new server running Windows Server 2008 R2. This old server has a few websites. Migrating a website is normally not difficult to do. Have done these migrations many times already. But one of the websites has hundreds of hostheaders attached to it. So this is not something you want to do by hand.

The good thing was that I had all these hostheaders already in a textfile. With the help of PowerShell it was easy to add these hostheaders to the website on the new server. I used this one-liner.

Get-Content c:\urls.txt | Foreach-Object {New-webbinding -name “Default Website” -IPAddress “*” -Port 80 -HostHeader $_}

Before you do this make sure you have added the WebAdministration module to your PowerShell console with Import-Module -Name WebAdministration.

Upload a file with ftp using PowerShell

Recently I needed a script to upload log files from one server to another server. The servers were in separate networks so I could not use a simple copy command like xcopy or robocopy. For this I had to use FTP. I had already a simple old batch file that uses the built-in ftp command. But I wanted to use PowerShell so that I could make it more advanced. After some searching on PowerShell websites I found some examples. I used these as a start for my function.

function Send-FileToFtp {
    [CmdletBinding()]
    [OutputType([System.Int32])]
    param(
        [Parameter(Position=0, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.Object]$SourceFile,

        [Parameter(Position=1, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Destination,
        
        [Parameter(Position=2, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$UserName,
        
        [Parameter(Position=3, Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [System.String]$Password
    )

    Process {
        try {
            # Get the object used to communicate with the server. 
            $Request = [System.Net.FtpWebRequest]::Create("ftp://$Destination/$($SourceFile.Name)") 
            $Request.Method = $Request.Method = [System.Net.WebRequestMethods+ftp]::UploadFile 
             
            # This example assumes the FTP site uses anonymous logon. 
            $Request.Credentials = New-Object System.Net.NetworkCredential $UserName,$Password 
             
            # Copy the contents of the file to the request stream. 
            $FileContents = [System.IO.File]::ReadAllBytes($SourceFile.FullName) 
            $Request.ContentLength = $fileContents.Length
            $RequestStream = $request.GetRequestStream() 
            $RequestStream.Write($FileContents, 0, $FileContents.Length) 
            $RequestStream.Close()
            $Response = $Request.GetResponse() 
            $Response.StatusDescription
        }
        catch {
            throw "ERROR: Unable to upload file $($SourceFile.name), status {0}"
        }
        $Response.StatusDescription
        }
    }

The function takes 4 parameters as you can see in the script. Most of them will be self explaining. But the parameter $SourceFile will need some explanation. Instead of using a string I decided to use an object. I did this so I can use the object to construct the filename of the source and also use it to construct the ftp request. To use the function you can write something like this.

$files = Get-ChildItem -Path "c:\windows\system32\logfiles\" -Filter "*.log"
if ($files -ne $null) {
    Foreach ($file in $files) {
        $result = Send-FileToFtp -SourceFile $file -Destination "ftp.domain.com/folder" -UserName "user" -Password "password"
        if ($Result -match "226*"){
            "Upload completed, now deleting source file: $($File.Name)"
        } else {
            "Upload failed!"
        }
    }
}

It gets all the log files from the c:\windows\system32logfiles directory and places them in the variable $files. For every file object in the variable $files it runs the function Send-FileToFtp. If the upload succeeds, the function returns a StatusDescription with the code 226, you can delete the original file or do something else. When the upload fails you can output a message or do somethings else.

For what I wanted to do this works perfect. But for other things it possibly needs to get changed a bit.

How to change the primary group of an AD user?

In the past I have made a PowerShell script that did some cleanup in the Active Directory on accounts of former employees. Like disable their account, move to a different OU and archive home and profile directory. In that script I use the Quest AD Cmdlets for all the AD actions. Although I like these cmdlets, I wanted to modify the script so that it uses the built-in AD cmdlets that are built in Windows Server 2008 R2.

Replacing the Quest AD Cmdlets with the built in cmdlets was not very difficult. But I had one little problem. We prefer to remove old accounts from all the groups they belong to. This way you will not find disabled or not existing accounts in the groups. But one group is assigned as the Primary Group and you cannot remove a user from this group. Default this is the ‘Domain Users’ group.

In the old script we added the user account to a special group called ‘Former Employees’, make this the Primary Group and remove all the other groups including ‘Domain Users’. With the Quest AD Cmdlets is fairly easy to do with the cmdlet Set-QADuser. Unfortunately this was not possible with the cmdlet Set-ADuser.

I tried to find a solution with Google but was not very successful. All the answers I found refer to ADSI or the Quest AD Cmdlets. So I have made my own function for this using the built-in AD Cmdlets from Windows Server 2008 R2. The user has to be a member of the group before you can make it the Primary Group!

Function Set-PrimaryGroup {
    Param (
        [string]$username=$(Throw "Error: Please enter a username!"),
        [string]$groupname="Domain Users")

Process {
    Try {
        #Get the DistinguishedName of the user for you want to change the primary group
        $UserDistinguishedName = (get-aduser -Identity $username -ErrorAction Stop).DistinguishedName
        Try {
            #Get SID of the Group that will become the primary group
            $GroupSid = (Get-ADGroup -Identity $groupname -Properties PrimaryGroupID -ErrorAction Stop).SID

            #Get the last 4 digits of the SID to become te PrimaryGroupID
            $PrimaryGroupID = $GroupSid.Value.Substring($groupsid.Value.LastIndexOf('-')+1)

            #Replace the PrimaryGroupID of the user with its new value. The user has to be already a member of this group.
            Set-ADObject -Identity "$UserDistinguishedName" -replace @{PrimaryGroupID=$PrimaryGroupID}
            Write-Host "$groupname set as primary group for user $username"
        } Catch {
            Write-Host "Error: Unable to find the PrimaryGroupID for group $GroupName! Maybe the group does not exist." -ForegroundColor red
        }
    } Catch {
        Write-Host "Error: Unable to find user $username!" -ForegroundColor red
    }
  }
}