Monday, 15 August 2016

My Ten Tips For Technical Writing

I have written many hundreds of thousands of words over my career – not only work product, but books, trade publication articles and so on. Over that time, I’ve read far more then I’ve written. And in that time, I’ve slowly developed a set of rules for how I write. Or for how I hope to write!

In my career, I have had three great writing mentors: Keith Burgess, Roy Chapman, and Susan Greenberg. Keith was a partner at what was then Arthur Andersen Management Consultants who I worked directly throughout my time there (and was to work for again 20 years later). I did work for Roy over the years, he was a master at writing. Susan Greenberg was an amazing instructional designer at Microsoft and a great teacher. All these people strove for good writing – and helped me to learn to write better.

My ten rules are really pretty straightforward. Some can easily be converted in to muscle memory – your hands just type better text. And when I see some of my rules broken in other people’s writing – it drives me nuts. Those authors who have had me as an editor will recognise some of them!

So here are my ten rules for better writing:

  1. Avoid future tense  This rule has a couple of benefits. Some languages deal with future tense differently to English – present tense is easier. Also future tense may make the writing harder to understand. For example: if you say “clicking on the foo button will make bar happen”. So the question is when will that happen? Is it immediate? Will bar happen in a minute/hour/day? Better to say: ‘Clicking on the foo button makes bar happen.
  2. Avoid passive voice Passive voice is, to cite Wikipedia, is a grammatical construction where the noun that would be the object of an active sentence appears as the subject of the sentence.  Active voice, for example “Our company builds the best widgtets to “The best widgets are built by our company. Sentences with passive voice add words to the sentence (6 vs 8 words). They can also make the reader work harder to understand the intended meaning.
  3. No split infinitives Yeah – I know To Boldly Go and all that.  But an infinitive is a single idea – splitting it makes understanding more difficult. You should avoid that.
  4. What is it, so what  This is one of the lessons Susan Greenberg beat into us. It’s simple really: when you are writing about something, you should explain what is it, before telling us why it matters. I really hate reading about some product or technology where the writer spends the first paragraph telling me why it was cool, without ever explaining precisely what ‘it’ actually is.
  5. Organise carefully I learned this lesson watching Keith Burgess. When you are writing a client report, or a magazine article, you have a purpose. That purpose could be to justify a project, or to explain a product or product feature. You need to organise your thoughts carefully, progressing from premise through to conclusion. You are taking the reader on the journey. We all know authors who veer widely off topic with annoying regularity.
  6. Decide on 2nd person vs 3rd person This is about how you are talking to the reader. Do you say: “You do X to make the Y feature work” or “The user does X to make the Y feature work”.  I prefer to directly to the reader, you, vs some one else, eg the user, the manager, the IT Pro.
  7. Gender neutrality - or not This is one where political correctness can abound. I see some writers trying to be cute and always using ‘she’, or ‘chairwoman, or those being politically correct, eg the chairperson. I have views here – but the level of neutrality you need will depend on the target audience.
  8. The Oxford Comma This one is subjective – some ignore it, others insist. It’s called Oxford command because it was used by printers, readers, and editors at Oxford University Press. It can clarify the meaning of a sentence when the items in a list are not single words.
  9. Be balanced I like to see the good news AND the bad news. Nothing I hate more than pure puff pieces – ones that just say good things about a product. Likewise, I dislike totally negative articles. I’ve prefer writers that see both sides, who are balanced in their coverage. NO product is perfect.
  10. If you can't say something nice, say nothing at all  Rarely, but on occasion, I’ve had occasion to use this rule. I’ve been asked to write about products or technologies I just do not believe in. I’ve found it easier to just not write an article than write one that is just negative.

That’s it. Simple really!

Friday, 6 February 2015

Install-RMAzureVmCert

Function Install-WinRMAzureVmCert {
<#
.SYNOPSIS
Downloads and installs the certificate created or
initially uploaded during creation of a Windows based
Azure Virtual Machine.
.DESCRIPTION
Downloads and installs the certificate created (or uploaded)
during the creation of a Windows based Azure Virtual Machine.
Running this function obtains and installs the certificate into
your local machine certificate store. Writing to the localhost's
cert store requires PowerShell to run elevated. Once the
certificate is installed, you can connect to Azure VMs using SSL
to improve security.
.NOTES
File Name : Install-WinRmAzureVmCert.ps1
Author : Thomas Lee - tfl@psp.co.uk
Requires : PowerShell Version 3.0, Azure module 8.12
Tested : PowerShell Version 5
.PARAMETER SubscriptionName
The name of the Azure subscription whose VMs you want to get
certificates from. Use quotes around subscription names
containing spaces.
.PARAMETER ServiceName
The name of the Azure cloud service the virtual machine is
deployed in.
.PARAMETER VmName
The name of the Azure virtual machine to install the
certificate for.
.EXAMPLE
Install-WinRmAzureVMCert -SubscriptionName "my subscription" `
-ServiceName "mycloudservice" -Name "myvm1"
#>
[Cmdletbinding()]
param(
[string] $SubscriptionName,
[string] $CloudServiceName,
[string] $VMName)

Function IsAdmin
{
Write-Verbose 'Checking user is an Admin'
$IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
Write-Verbose "User is admin: [$IsAdmin]"
Return $IsAdmin
}

# First, ensure the user is admin and that the VM exists
if((IsAdmin) -eq $false)
{
Write-Error "Must run PowerShell elevated to install WinRM certificates."
return
}
If (-not (Get-AzureVM -ServiceName $CloudServiceName -Name $VMName))
{
Write-Error "VM $VMName does not exist."
return
}

# Pre-reqs OK so let's get started...
Write-Verbose "Getting WinRM Certificate for Service: [$CloudServiceName] and VMname: [$VMName]"
$AzureVM = (Get-AzureVM -ServiceName $CloudServiceName -Name $VMname).vm
$WinRmVmTp = $AzureVM.DefaultWinRMCertificateThumbprint
$AzureX509cert = Get-AzureCertificate -ServiceName $CloudServiceName -Thumbprint $WinRmVmTp -ThumbprintAlgorithm sha1
Write-Verbose "Found certificate with thumbprint: $WinRmVmTp"

# Now get cert into our cert store
# First create a temp file and dump the certificate data to it
$CertTempFile = [IO.Path]::GetTempFileName()
Write-Verbose "Using temp file: [$CertTempFile]"
$AzureX509cert.Data | Out-File $CertTempFile
Write-Verbose 'Temp file contains cert data'

# Create a certificate object from this file
$CertToImport = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $certTempFile

# Now get the local machine's trusted root store
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "LocalMachine"
Write-Verbose "[$($store.location)] [$($store.name)] cert store found sucessfully"

# Now Add the cert object to the store
$store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite)
$store.Add($CertToImport)
$store.Close()
Write-Verbose 'Certificate written to store'

# And nuke the temp file
Remove-Item $certTempFile
Write-Verbose 'Temp file removed'
}
Technorati Tags: ,

Sunday, 1 February 2015

New-HttpVmEndpoint.ps1

Function New-HttpVmEndpoint {
<#
SYNOPSIS
This script defines a function to add an HTTP endpoint
to an Azure VM.
.DESCRIPTION
This script uses the Azure module to change the VM instance size.
.NOTES
File Name : New-HttpVmEndpoint.ps1
Author : Thomas Lee - tfl@psp.co.uk
Requires : PowerShell Version 3.0, Azure module
Tested : PowerShell Version 5
.LINK
This script posted to:
http://www.pshscripts.blogspot.com

.EXAMPLE
New-HttpVmEndpoint -VmName 'psh1' -ServiceName 'psh1'

OperationDescription OperationId OperationStatus
-------------------- ----------- ---------------
Update-AzureVM 99709683-8c4b-607e-8856-5958a6967147 Succeeded

#>
[Cmdletbinding()]
Param (
$VmName,
$ServiceName
)
# So here, get the vm, add the endpoint, then update the VM
Get-AzureVM -ServiceName $ServiceName -Name $VmName |
Add-AzureEndpoint -Name "Http" -Protocol "tcp" -PublicPort 80 -LocalPort 80 |
Update-AzureVM

}

# And Test It
New-HttpVmEndpoint -vm 'psh1' -servicename 'psh1'

 

Technorati Tags: ,,,

Monday, 19 January 2015

AzureVM.Format.Ps1xml

<!--

   Description
      This file contains display XML for the object returned from
      Get-AzureVM. It's purpose is to provide better (default) output
      from Get-AzureVm.
   Written by
     
Thomas Lee (tfl@psp.co.uk
   Copyright:
    
PS Partnership 2015
-->
<?xml version="1.0" encoding="utf-16"?>
<Configuration>
  <ViewDefinitions>
    <View>
      <Name>AzureVM</Name>
      <ViewSelectedBy>
        <TypeName>Microsoft.WindowsAzure.Commands.ServiceManagement.Model.PersistentVMRoleListContext</TypeName>
      </ViewSelectedBy>
      <TableControl>
      <AutoSize />
        <TableHeaders>
          <TableColumnHeader>
             <Label>Name</Label>
          </TableColumnHeader>
          <TableColumnHeader>
             <Label>Service Name</Label>
          </TableColumnHeader>
          <TableColumnHeader>
             <Label>Status</Label>
          </TableColumnHeader>
          <TableColumnHeader>
              <Label>Hostname</Label>
          </TableColumnHeader>
          <TableColumnHeader>
              <Label>FQDN</Label>
          </TableColumnHeader>
          <TableColumnHeader>
              <Label>Instance Size</Label>
          </TableColumnHeader>         
          <TableColumnHeader>
              <Label>IP Address</Label>
          </TableColumnHeader>
        </TableHeaders>
        <TableRowEntries>
          <TableRowEntry>
            <TableColumnItems>
              <TableColumnItem>
                <PropertyName>Name</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>Servicename</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>status</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>hostname</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>DNSName</PropertyName>
              </TableColumnItem>             
              <TableColumnItem>
                <PropertyName>instancesize</PropertyName>
              </TableColumnItem>
              <TableColumnItem>
                <PropertyName>IPaddress</PropertyName>
              </TableColumnItem>
            </TableColumnItems>
          </TableRowEntry>
        </TableRowEntries>
      </TableControl>
    </View>
  </ViewDefinitions>
</Configuration>

Thursday, 4 December 2014

Measure-TypeAccelerator.ps1

<#
.SYNOPSIS
This function 'measures' (counts) the number of
Type Accelerators on your system.
.DESCRIPTION
This function counts the number of type accelerators are
on your systems and returns that number.
.NOTES
File Name : Measure-TypeAccelerator.ps1
Author : Thomas Lee - tfl@psp.co.uk
Requires : Version 3
.LINK
Script Repository
http://www.pshscripts.blogspot.com
.Example
Psh[C:\foo]> Measure-TypeAccelerator.ps1
84
.Example
Psh[C:\foo]> Count-TypeAccelerator.ps1
84

#>

Function Measure-TypeAccelerator {
# Define parameters and enable advanced functions
# NB no parameters!
[cmdletbinding()]
Param ()

# Start of function
Write-Verbose 'Getting acount of all Type Accelerators'
$Count = (([PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')::Get).GetEnumerator() |
Measure).count
Write-Verbose "$Count Type Accelerators found on $(hostname)"
Return $count
}

# Set an alias
Set-Alias CTA Measure-TypeAccelerator
Set-Alias MTA Measure-TypeAccelerator

Wednesday, 3 December 2014

Remove-TypeAccelerator

<#
.SYNOPSIS
This script removes a type accelerator from your system
.DESCRIPTION
This script removes a NEW TA from your system.
.NOTES
File Name : Remove-TypeAccelerator.ps1
Author : Thomas Lee - tfl@psp.co.uk
Requires : Version 3
.LINK
Script Repository
http://www.pshscripts.blogspot.com
.Example
Psh[C:\foo]> Remove-TypeAccelerator tfl
Alias [tfl] removed
#>

###
# Start of script
###

Function Remove-TypeAccelerator {
[cmdletbinding()]
param (
[Parameter(Mandatory=$true)]
[string] $alias
)

# Start of function
Try
{
[void] ([PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::remove($alias))
}
Catch
{
Write-Error "Failed to remove alias [$alias]"
return
}

# Return
"Alias [$alias] removed"
}

Set-Alias rta Remove-TypeAccelerator

# Test this out
Remove-TypeAccelerator 'foo3'

Tuesday, 2 December 2014

New-TypeAccelerator.ps1

<#
.SYNOPSIS
This script creates a new type accelerator on your system
.DESCRIPTION
This script adds a NEW TA to your system.
.NOTES
File Name : New-TypeAccelerator.ps1
Author : Thomas Lee - tfl@psp.co.uk
Requires : Version 3
.LINK
Script Repository
http://www.pshscripts.blogspot.com
.Example
Psh[C:\foo]> New-TypeAccelerator tfl system.int32
Alias [tfl] added for type [system.int32]
#>

###
# Start of script
###

function New-TypeAccelerator {
[cmdletbinding()]
param (
[Parameter(Mandatory=$true)]
[string] $alias,

[Parameter(Mandatory=$true)]
[string] $type
)

# Start of function
Try
{
([PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::add($alias,$type))
}
Catch
{
Write-Error "Failed to add alias [$alias] to type [$type]"
return
}

# Return
"Alias [$alias] added for type [$Type]"
}

Set-Alias nta New-TypeAccelerator

# Test this out
New-TypeAccelerator

Monday, 1 December 2014

Get-TypeAccelerator.ps1

<#
.SYNOPSIS
    This script defines a function to get a list of
    Type Accelerators in PowerShell and displays them nicely
.DESCRIPTION
    This script gets the details of type accelerators in the system. 
    Earlier versions of this script uses a different class, which
    has been taken private and is not available any more. This 
    script also creates an alias for the function. GTA takes a
    string parameter which is used as a regular expression to 
    find a subset of type accelerators.
.NOTES
    Additional Notes, eg
    File Name  : Get-TypeAccelerator.ps1
    Author     : Thomas Lee - tfl@psp.co.uk
    Requires   : Version 3
.LINK
    Original article:
      http://www.nivot.org/2008/12/25/ListOfTypeAcceleratorsForPowerShellCTP3.aspx
    Script Repository
      http://www.pshscripts.blogspot.com
.Example
    Psh[C:\foo]>Get-TypAccelerator int
     Name   Type                      
    ----   ----                      
    bigint System.Numerics.BigInteger
    int    System.Int32              
    int16  System.Int16              
    int32  System.Int32              
    int64  System.Int64              
    uint16 System.UInt16             
    uint32 System.UInt32             
    uint64 System.UInt64       

.Example
    Psh[C:\foo]>Get-TypAccelerator 's$'
    Name              Type                                                   
    ----              ----                                                   
    Alias             System.Management.Automation.AliasAttribute            
    cimclass          Microsoft.Management.Infrastructure.CimClass           
    ipaddress         System.Net.IPAddress                                   
    mailaddress       System.Net.Mail.MailAddress                            
    SupportsWildcards System.Management.Automation.SupportsWildcardsAttribute
    wmiclass          System.Management.ManagementClass    
#>

###
#   Start of script
###
Function Get-TypeAccelerator {
[Cmdletbinding()]
param (
  [string] $accelerator
)

([PSObject].Assembly.GetType("System.Management.Automation.TypeAccelerators")::Get).getenumerator() |
  Select-object @{Name="Name"; expression={$_.key}},  
                @{name="Type"; expression={$_.value}} | 
  where name -match $accelerator | Sort name | Format-Table -Autosize
}
Set-Alias gta Get-TypAccelerator

# Test script
Get-TypAccelerator  int  # anything with int 
Get-TypeAccelerator 's$' # ends in s

Friday, 28 November 2014

Get-Stack1.ps1


<#
.SYNOPSIS
MSDN sample showing push and other stack processing using PowerShell
.DESCRIPTION
This script creates a script then performs stack operations.
.NOTES
File Name : Get-Stack1.p1
Author : Thomas Lee - tfl@psp.co.uk
Requires : PowerShell V2
.LINK
http://www.pshscripts.blogspot.com
.EXAMPLE
PSH [C:\foo]: .\get-stack1.ps1'
Stack at start:
fox
quick
The

(Pop) fox
Stack value after Pop:
brown
quick
The

(Pop) brown
Stack values after 2nd pop:
quick
The

(Peek) quick
Stack values after a peek:
quick
The
#>

##
# start of script
###

# Create and initialise a new stack object
$mystack = new-object system.collections.stack
$myStack.Push( "The" )
$myStack.Push( "quick" )
$myStack.Push( "brown" )
$myStack.Push( "fox" )

# Display the Stack
"Stack at start:"
$myStack
""# Pop an element from the Stack.
"(Pop)`t`t{0}" -f $myStack.Pop()
"Stack value after Pop:"
$myStack
""

# Pop another element from the Stack
"(Pop)`t`t{0}" -f $myStack.Pop()

# Display the Stack after 2nd pop
"Stack values after 2nd pop:"
$myStack
""

# Peek at the front
"(Peek)`t`t{0}" -f $myStack.peek()

# Display the Stack after the peek
"Stack values after a peek:"
$myStack

Thursday, 27 November 2014

Zip-Pshscripts3.ps1

#Requires –Version 5.0
#
#.Synopsis
#    Creates a zip file of PowerShell scripts 
#.Description
#    The script creates a zip file containing all the files, 
#    recursing through the top level PowerShell Script Library folder.
#
#.Notes
#    This script require PowerShell V5 for the zip file cmdlets!
#    Author - Thomas Lee - tfl@psp.co.uk
#
#.Example
#    PS [c:\foo]>  .\Zip-PSHScripts3.ps1
#    Total files  : 347
#    ps1 files    : 342
#    txt files    : 1
#    other  files : 4


# Define what to zip and from where
$zipfile  = "C:\foo\ScriptLib.ZIP"
$zipfrom  = "C:\Users\tfl\Dropbox\PowerShell Script Library (master)"
$recurse  = "true"
$ziptoadd = "ps1"

# Check it out
if ( ! (Test-path -Path $zipfrom ))
{
  Write-Host 'scripts folder does not exist'
}

# Zip it up!
Try 
  {
    Compress-Archive -Path $zipfrom -DestinationPath $zipfile -CompressionLevel Optimal -Update
  }
Catch 
  {
  Write-Host ' Error Zipping up the script library'
  $Error[0]
  }

# Stats
$files = ls $zipfrom -file -recurse
$files_ps1 = $files | Where-Object Extension -eq '.ps1'
$files_txt = $files | Where-Object Extension -eq '.txt'
$files_other = $files | Where-Object { $_.extension -NE '.PS1' -and $_.Extension -ne '.txt'} 

"Total files  : {0}" -f $($files.count)
"ps1 files    : {0}" -f $($files_ps1.count)
"txt files    : {0}" -f $($files_txt.count)
"other  files : {0}" -f $($files_other.count)


# All done
ls $zipfile