Friday, October 18, 2013

Invoke-RestMethod and Invoke-WebRequest Header and Other Problems

I'm working on a script that copies the contents of a Rackspace Cloud Files container to another container.  I'm leveraging Rackspace's PowerShell library from GitHub and adding what features it currently does not possess.  One such feature is a function to copy a Cloud Files Object.  The API documentation for this specifies 2 ways to do it.  So here's my story:

  1. I first went the path of using the "COPY" method.  This effort was quickly thwarted when it became obvious that Invoke-WebRequest's -Method does not support Copy for a value.  On to #2.
  2. I began working on the "PUT" method.  This approach requires you to specify the size of the file being copied in the header as the "Content-Length".  So I added "Content-Length" to the headers and it gives me this error:
    This header must be modified using the appropriate property or method.
    Parameter name: name

    So, there's no way to add content-length to the Invoke-WebRequest or Invoke-RestMethod functions...  What now?
  3. I rolled the dice on Google for a while until I came across this Microsoft Connect bug.  Taking a quick look at the workarounds, Ch15Murray suggests tapping into some .NET features.  Here's what it looks like:

    $webRequest = [System.Net.WebRequest]::Create( $url )
    $webRequest.PreAuthenticate = $true
    $webRequest.Method = "POST"
    $webRequest.Headers.Add('Authorization', "Basic $token")
    $webRequest.Accept = 'application/*+xml;version=5.1'
    $webRequest.GetResponse()
    
I noticed that I can change the method property here without any validation, so I could use "Copy".  This takes me back to my first attempt which seems much easier.  Now I have a nice little approach to taking on the copy of a Rackspace Cloud Files object.  Here's my final code:
    $webRequest = [System.Net.WebRequest]::Create($rackspaceSourceUrl)
    $webRequest.PreAuthenticate = $true
    $webRequest.Method = "COPY"
    $webRequest.Headers.Add("Destination", $destinationPath)
    $webRequest.Headers.Add("X-Auth-Token", $token)
    $webRequest.GetResponse()

Monday, October 14, 2013

PowerShell Script with multiple Invoke-Sqlcmd calls

People need reports, but I don't like spending too much time putting them together.  That's why I'm looking into using PowerShell to build my reports for me.  I currently use 4 or 5 different SQL queries that hit different servers to acquire numbers for my report.  I'm starting small and hope that by the time I'm done with this script that it will fill in all of the nice bits in an Excel spreadsheet.  So, let's talk about my first snag:

When I began working on this, I found that the best way to get what I needed was to change what SQL scripts I had into scripts that return a single data set.  This means creating temporary tables and filling them with key/value pairs.  Then for the output, I would simply select * from the temporary table.

My next snag was when I needed to add a reference to a second SQL script to my PowerShell script.  I'm calling Invoke-Sqlcmd because the -inputfile parameter allows me to specify a file instead of a single query.  When I ran this, I found that only my first Invoke-Sqlcmd was writing to the screen.  To work around this, I set the result of the Invoke-Sqlcmd to a local variable and then called Format-Table with the new local variable.  Now I have one screen that gives me all of the data I need to throw into my Excel spreadsheet instead of having to go to 2 different SQL scripts.


Add-PSSnapin sqlservercmdletsnapin100
Add-PSSnapin sqlserverprovidersnapin100

$result1 = Invoke-Sqlcmd -inputfile "UsageCount.sql" -Server "server1"
$result2 = Invoke-Sqlcmd -inputfile "Incidents.sql" -Server "server2"

Write-Host "-----------------"
Write-Host
Format-Table -inputobject $result1
Format-Table -inputobject $result2

Thursday, October 3, 2013

PowerShell Curl Awesomeness

I needed to translate a curl command into a PowerShell command the other day for some reporting that I need to get done with Rackspace's Cloud Files solution.

Before I could get started, I grabbed some of the Rackspace PowerShell bits at http://developer.rackspace.com/blog/powerclient-rackspace-cloud-api-powershell-client.html.  It turns out that, as of my time of need for this module from Rackspace, the module had no Cloud Files features in it.  So I rolled my own.  I was given a quick curl command from the fanatical support tech at Rackspace to grab the data I needed for my reports.  I'm working on learning PowerShell, so I decided to find a nice PowerShell way to get my curl goodies.  Most folks will say that you should use Invoke-RestMethod, but the problem with this approach is that it does not return any headers.  Rackspace returns the information I need in the header.  Instead, I need to use Invoke-WebRequest.  The return value has a Headers property that gives you access to the response headers.  So my cool little script looks like this:



function WriteOutFileStats($count, $byteCount){
Write-Host ([string]::Format("Files = {0:N0}", $count))
$gbCount = $byteCount / 1024 / 1024 / 1024
Write-Host ([string]::Format("Size(GB) = {0:N2}", $gbCount))
}

Write-Host "Rackspace------------------------------------------------------------------------------"
Import-Module PowerClient
Get-AuthToken
$rackspaceUrl = ""
foreach ($service in $token.access.serviceCatalog.service)
{
    if ($service.name -eq "cloudFiles")
    {
        foreach ($endpoint in $service.endpoint)
        {
            if ($endpoint.region -eq "ORD")
            {
                $rackspaceUrl = $endpoint.publicURL
            }
        }
    }
}
$resp = Invoke-WebRequest -Uri $rackspaceUrl -Headers $HeaderDictionary -Method Head
WriteOutFileStats $resp.Headers["X-Account-Object-Count"] $resp.Headers["X-Account-Bytes-Used"]
Write-Host (Get-Date)