Tweet from Powershell using oAuth

I wanted to be able to post tweets from a Powershell script. There are alot of old blog posts and examples of composing tweets before Twitter introduced oAuth. I've seen a few examples of Powershell scripts that can post tweets using external dll's to handle the oAuth authentication. I wanted to keep my script self contained and only use .NET Framework assemblies where necessary and not rely on 3rd party dlls.

The first step is to create a Twitter application by logging on to http://dev.twitter.com. Complete the Application Details making sure to set the Application Type - Access to at least Read and Write. Make a note of the following:

  • Consumer key
  • Consumer secret
  • Access token
  • Access token secret

You'll need to update the associated Powershell script variables with your real values.

[Reflection.Assembly]::LoadWithPartialName("System.Security")
[Reflection.Assembly]::LoadWithPartialName("System.Net")

$status = [System.Uri]::EscapeDataString("My first tweet from Powershell");
$oauth_consumer_key = "<consumer key>";
$oauth_consumer_secret = "<consumer secret>";
$oauth_token = "<auth token>";
$oauth_token_secret = "<auth token secret>";
$oauth_nonce = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes([System.DateTime]::Now.Ticks.ToString()));
$ts = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null).ToUniversalTime();
$oauth_timestamp = [System.Convert]::ToInt64($ts.TotalSeconds).ToString();

$signature = "POST&";
$signature += [System.Uri]::EscapeDataString("http://api.twitter.com/1/statuses/update.json") + "&";
$signature += [System.Uri]::EscapeDataString("oauth_consumer_key=" + $oauth_consumer_key + "&");
$signature += [System.Uri]::EscapeDataString("oauth_nonce=" + $oauth_nonce + "&"); 
$signature += [System.Uri]::EscapeDataString("oauth_signature_method=HMAC-SHA1&");
$signature += [System.Uri]::EscapeDataString("oauth_timestamp=" + $oauth_timestamp + "&");
$signature += [System.Uri]::EscapeDataString("oauth_token=" + $oauth_token + "&");
$signature += [System.Uri]::EscapeDataString("oauth_version=1.0&");
$signature += [System.Uri]::EscapeDataString("status=" + $status);

$signature_key = [System.Uri]::EscapeDataString($oauth_consumer_secret) + "&" + [System.Uri]::EscapeDataString($oauth_token_secret);

$hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
$hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($signature_key);
$oauth_signature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($signature)));

$oauth_authorization = 'OAuth ';
$oauth_authorization += 'oauth_consumer_key="' + [System.Uri]::EscapeDataString($oauth_consumer_key) + '",';
$oauth_authorization += 'oauth_nonce="' + [System.Uri]::EscapeDataString($oauth_nonce) + '",';
$oauth_authorization += 'oauth_signature="' + [System.Uri]::EscapeDataString($oauth_signature) + '",';
$oauth_authorization += 'oauth_signature_method="HMAC-SHA1",'
$oauth_authorization += 'oauth_timestamp="' + [System.Uri]::EscapeDataString($oauth_timestamp) + '",'
$oauth_authorization += 'oauth_token="' + [System.Uri]::EscapeDataString($oauth_token) + '",';
$oauth_authorization += 'oauth_version="1.0"';

$post_body = [System.Text.Encoding]::ASCII.GetBytes("status=" + $status); 
[System.Net.HttpWebRequest] $request = [System.Net.WebRequest]::Create("http://api.twitter.com/1/statuses/update.json");
$request.Method = "POST";
$request.Headers.Add("Authorization", $oauth_authorization);
$request.ContentType = "application/x-www-form-urlencoded";
$body = $request.GetRequestStream();
$body.write($post_body, 0, $post_body.length);
$body.flush();
$body.close();
$response = $request.GetResponse();

14 comments:

Anonymous said...

Great post...

but it's not working. To test, I'm using the script unmodified (except for the consumer and token key/secret strings).

I'm getting the response, "The remote server returned an error: (401) Unauthorized."

Any thoughts?

Rick

Ian Chivers said...

Hi Rick,

Are you using a proxy server ?

Cheers,
Ian.

Peter Kriegel said...

Hello Ian!

Thank you this works really fine for me !

Peter Kriegel
http://www.admin-source.de
See you on Twitter ;-)

Anonymous said...

Great script !
One remark. I had a problem with $ts
Be careful, when use it. This part might be tricky, when you have Cultureinfo other than 'en-US'
You can replace $null with $culture
and $culture=New-Object System.Globalization.CultureInfo("en-US")
Should work ..

Anonymous said...

I get a return {"request":"\/1\/statuses\/update.json","error":"Timestamp out of bounds"} when I run the script. I'm in eastern timezone.

Has anyone else had this problem?

Anonymous said...

I found my problem. I seeing {"request":"\/1\/statuses\/update.json","error":"Timestamp out of bounds"} when I was troubleshooting with a program called Fiddler. I changed $oauth_timestamp to epoch time from http://www.epochconverter.com/ and was able to tweet successfully. Maybe twitter just takes Epoch time with no offset?

This is an excellent post. Thanks for sharing!


Zach

Anonymous said...

Doesn't work - error:

Exception calling "GetResponse" with "0" argument(s): "The remote server returned an error: (401) Unauthorized."
At line:47 char:1
+ $response = $request.GetResponse();
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : WebException

Deef said...

This still works for v1.1 of the twitter api.
You just need to change the urls.

Anonymous said...

I've tried this on two different accounts now. Keep getting the 401 errors.

I made the second account with the twitter permissions set to read/write/direct with sign-on allowed. I copypasta'd your code right into PowerShell+ and updated the tokens. I've even gotten it to work by calling the curl request generated on the OAuth tool that creates a self-contained post and authorization strings, and that worked. So I can only assume it's a code issue at this point.

Going to post it to the dev site and maybe I'll get some idea from there. Will post back if I do.

Anonymous said...

Hi All, figured out the issue it does deal with time, what I noticed from the epoch site http://www.epochconverter.com/ was that my time was incorrect or off and I figured it was just a GMT issue and found that the command .touninversaltime was subtracting another 8 hours (PST) (28800 sec) which created the time offset. So to fix it just remove that portion of the code. $ts = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null);#.ToUniversalTime();

Anonymous said...

Sorry not to be confusing just remove .ToUniversalTime(); from that line of code.

paperlessme said...

Could someone care to share their working code and setup with the 1.1 version of the API?

Geoff Shorts said...

Hi! To the folks getting 401 errors - make sure you have made your app read write.

If you originally created a read only app you can't just change the settings - you have to regenerate the keys. Worth making a new app from scratch as a troubleshooting step.

My thanks to Ian for the great code.

Anonymous said...

Check this simple ps script that can access any of the v 1.1 TwitterAPIs including the Streaming api...
https://github.com/MeshkDevs/InvokeTwitterAPIs