AD Connect – password sync notifications

For a customer I setup AD Connect (AD Sync) along with password sync. However the customer needed more visibility and an easy way to be notified when passwords were actually changed in Azure AD (Office 365). Passwords can be changed with on-prem Active Directory (AD), however it’s a slight delay when the password sync actually makes the actual password change in Azure AD – the slight delay being up-to 2 minutes.

The scenario being if a user needed their password changed by IT, IT can change the password and simply say to the user “when you get an SMS, you’ll be able to log on”. The user can hang up without waiting any further, when the SMS is sent through they can confidently logon with their new password.

The way it works, a PowerShell script is configured as an action to a specific scheduled task which monitors the event log for an Application based event ID 656 and sends an SMS text message to the admin as well as the end user as soon as password sync has taken place.

To setup the scheduled task, simply have a look in the Application event log for an existing event ID 656 on the AD Connect server, right click this and choose Attach Task To This Event.

2016-02-02_1545

The action for the scheduled task is setup like this below, however in the real world you might not want to put the PowerShell script on the desktop.

2016-02-09_1235

The requirement is for the user to have their mobile number setup in Active Directory. This is based on Australian mobile numbers and the PowerShell script below is setup to handle and accept mobile numbers in formats being “0401234567” or “+61401234567“. The PowerShell script further below sources the mobile number field out of Active Directory based on the user who is having their password changed and the search happens in real time.

2016-02-09_1213

Based on access needed to Active Directory and access to the event log on AD Connect, obviously the best and only place to have this PowerShell based scheduled task setup is on the AD Connect (AD Sync) server itself (unless you use Remote PowerShell). However, you would need to install the AD PowerShell module on the AD Connect server.

Install-WindowsFeature -Name RSAT-AD-PowerShell

As for the SMS text messages, you can setup and register an account with https://dev.telstra.com/. Once the account has been approved, you can then setup an app for the purpose of sending SMS’s using the Telstra SMS API. Please note, when the Telstra site asks for the callback URI, this can be any regular website – for what we are using it for this will suffice.

Below is the PowerShell code which does all the work and what is setup as the action in the scheduled task. Please note, this is not perfect, meaning, the thought process behind this is if a single user has their password changed within the space of a single password sync session, this is classed as a single user password change and an SMS is sent to both the user and the administrator. Remember, it takes roughly up-to 2 minutes for password sync to take place and if you do two single user password changes in a short space of time before password sync has had a change to run, then the two password changes would sync together and be classed as a bulk change. In the instance of more than one password sync’ing at anytime, this is classed as a bulk password change and the end user doesn’t get an SMS, only the administrator. Please note, also in the instance of a bulk password change the administrator would only get the information of the first user in the group of users that are having their passwords changed at once, as an SMS has a limitation or 160 characters.

$adminnumbers = @("0402345678", "0401234567")

# Get the latest event of the password change
$who = (Get-EventLog -LogName Application | where {$_.EventID -eq "656"} | select -First 1).Message | Select-String -Pattern 'CN=(.*?),' -AllMatches |
ForEach-Object {$_.Matches} |
ForEach-Object {$_.Groups[1].Value}

$when = (Get-EventLog -LogName Application | where {$_.EventID -eq "656"} | select -First 1).Message | Select-String -Pattern 'Change\sDate\s:\s(.*?)[\n]' -AllMatches |
ForEach-Object {$_.Matches} |
ForEach-Object {$_.Groups[1].Value}

if($who.count -eq 1){$message = $who + " password has been changed in O365: " + $when} else{$message = $who[0] + " password has been changed in O365: " + $when[0]}

#Get Telstra API access - https://dev.telstra.com/
$app_key = "xhJrpZ8EMsCB6sAS5hMZD2SIHG22QCR1"
$app_secret = "eKVUwbt8CMdyV1xo"
$auth_string = "https://api.telstra.com/v1/oauth/token?client_id=" + $app_key + "&client_secret=" + $app_secret + "&grant_type=client_credentials&scope=SMS"
$auth_values = Invoke-RestMethod $auth_string

# Send SMS to admin
foreach($adminnumber in $adminnumbers){
$tel_number = $adminnumber
$token = $auth_values.access_token
$body = $message.TrimEnd()
$sent_message = Invoke-RestMethod "https://api.telstra.com/v1/sms/messages" -ContentType "application/json" -Headers @{"Authorization"="Bearer $token"} -Method Post -Body "{`"to`":`"$tel_number`", `"body`":`"$body`"}"
$sent_message
}

if($who.count -eq 1){
# Connect to on-prem AD
Import-Module ActiveDirectory
$OU = 'OU=RT Users,DC=rtdc,DC=local'
$aduserrawmob = (Get-ADuser -SearchBase $OU -Filter * -Properties mobile | where {$_.Name -eq $who}).mobile
$adusermob = $aduserrawmob.replace('+61','').replace(' ','')

# Send SMS to user
$tel_number = $adusermob
$token = $auth_values.access_token
$body = $message.TrimEnd()
$sent_message = Invoke-RestMethod "https://api.telstra.com/v1/sms/messages" -ContentType "application/json" -Headers @{"Authorization"="Bearer $token"} -Method Post -Body "{`"to`":`"$tel_number`", `"body`":`"$body`"}"
$sent_message}

 

Remote PowerShell SSL / HTTPS / 5986

Right…. There’s a lot of articles online how to setup remote PowerShell or how to configure remote PowerShell. I have found that all articles on how to setup remote PowerShell are not all complete. As in there’s some information there, different parts of information all over the place across different posts and not in the complete order and/or missing steps.

You need Remote PowerShell to administer Windows servers and these days with PowerShell, you can do everything and anything with PowerShell, so remote PowerShell is a must. It’s even more so important now that the cloud is here and Azure is around who offer virtual machines with the port 5986 open by default.

Remote PowerShell is a little hard to setup and comes in two flavours, HTTP (port 5985) and HTTPS (port 5986). In the theme of security, this post will focus on the most secure way of setting up Remote PowerShell, port 5986 HTTPS with SSL. Also too, I am not focusing on domain based machines, I am focusing on just stock standard machines, machines not connected to the domain aka ‘workgroup’ servers.

First things first, you need to make a server signing certificate with a private key. Easiest and cheapest (free) way is to get copy of makecert and pvk2pfx available from here http://1drv.ms/1O70n3e.

Once you have these tools, you need to run: makecert -sky exchange -r -n “CN=*.yourdomain.com” -pe -a sha1 -eku 1.3.6.1.5.5.7.3.1 -len 2048 -ss My -sr localmachine “MyCert.cer” Use your best judgement here and change some things around as you see fit, this is an example only.

From here you import this certificate to the local machine and export the private key to a *.pfx file. The *.pfx file you will need to add to the machine you are connecting to and you will need to add the certificate authority certificate to the trusted root store of the machine you are connecting from. I won’t go into the certificate stuff too deep here, but you should know most of this already.

Below is the the real stuff that this post focuses on, some PowerShell in which you can run on the machine that you want to connect to. Run the entire first region first (down to line 28) as long as you’re sure you have the correct information for the certificate line. Again, use your best judgement here and change some things around as you see fit, this is an example script only. This first region will reset WinRM completely and then setup it up for just HTTPS and add the Windows firewall rule to allow for the connection. The other stuff below the first region is there just for information purposes only which you might or might not benefit from or may not need at all.

#region completely reset and configure SSL HTTPS based remote PowerShell on port 5986

#Restore remote PowerShell to the default state
$process = 'cmd.exe'
$arguments = '/c winrm invoke restore winrm/config @{}'
start-process $process -ArgumentList $arguments -Wait

#enable Server Manager Remoting
Configure-SMRemoting.exe -enable

#Enable WinRM
#winrm quickconfig -transport:https

#Enable WinRM
Enable-PSRemoting -SkipNetworkProfileCheck -Force

$cert = Get-ChildItem Cert:\LocalMachine\My | where {$_.Subject -match 'yourdomain.com' -and $_.HasPrivateKey -eq 'True' -and $_.Issuer -match 'Root Authority'}
Get-ChildItem WSMan:\Localhost\listener | Where -Property Keys -eq "Transport=HTTP" | Remove-Item -Recurse
Remove-Item -Path WSMan:\Localhost\listener\listener* -Recurse
New-Item -Path WSMan:\LocalHost\Listener -Transport HTTPS -Address * -CertificateThumbPrint $Cert.Thumbprint –Force

Restart-Service winrm
#endregion

#Check for the listener (random commands):
Get-Item WSMan:\localhost\Client\TrustedHosts
restart-Service winrm
winrm get winrm/config
winrm enumerate winrm/config/listener
winrm get winrm/config/service

#auth Certificate default setting
$process = 'cmd.exe'
$arguments = '/c winrm set winrm/config/service/auth @{Certificate="false"}'
start-process $process -ArgumentList $arguments -Wait

#auth Kerberos default setting
$process = 'cmd.exe'
$arguments = '/c winrm set winrm/config/service/Auth @{Kerberos="true"}'
start-process $process -ArgumentList $arguments -Wait

#Firewall settings - if you need them
New-NetFirewallRule -DisplayName "Windows Remote Management (HTTPS-In)" -Name "Windows Remote Management (HTTPS-In)" -Profile Any -LocalPort 5986 -Protocol TCP
New-NetFirewallRule -DisplayName "RemotePowerShell" -Direction Inbound –LocalPort 5985-5986 -Protocol TCP -Action Allow

#Combatability 443 listener - if you need it
Set-Item WSMan:\localhost\Service\EnableCompatibilityHttpsListener -Value true

After you run the above to configure remote PowerShell, you will need to connect. That’s a simple case of running the below PowerShell to connect to the remote machine. The script below is like a donut, there’s a top and tail, you can put what ever you like in the middle.

#To connect
$machineid = 'demo.yourdomain.com'
$adminname = 'local.admin'
$adminpassword = 'YoUrPaSsWoRd'
$password = ConvertTo-SecureString $adminpassword -AsPlainText -Force
$cred= New-Object System.Management.Automation.PSCredential (".\$adminname", $password)
Enter-PSSession -ComputerName $machineid -Port 5986 -UseSSL -Credential $cred

#To disconnect
Exit-PSSession

AADSync to AD Connect migration

My notes of doing a migration from AADSync to AD Connect (AD Sync). In the process I installed AD Connect onto a new server.  AD Connect uses AD Sync as it’s new sync service (which is the third release of the product). We went from DirSync > AADSync > ADSync.

Notable changes, to force a sync of the directory, this is done from scheduled tasks. Password sync runs in real time within under 2 minutes with an option to speed this up.

There is the immutableID thing which you don’t really need to worry about either as we are syncing from the same source. Remember, the immutableID attribute is used as a source anchor which is how the sync service matches up on-prem directory objects with Azure AD objects. The on-prem objectGUID for objects are encoded into base64 which results in this value being stamped as the immutableID attribute of Azure AD objects.

The process is not that hard really, in a nutshell, you install AD Connect on a new server, don’t use the express option, use the custom install option, go right through to the very end and enable staging mode.

2016-02-02_1050

Staging mode will setup the server like normal e.g. for a DR site, it will enable you to fully configure it, however it doesn’t make any changes to either AD (on-prem AD) or AAD (Azure AD).

When you’re ready to fully move the sync’ing to the new AD Connect, on the old or ‘current’ AADSync server, run the following PowerShell on AADSync to stop and disable the Sync’ing services.

Get-Service | where {$_.DisplayName -match "forefront identity"} | Set-Service -StartupType Disabled
Get-Service | where {$_.DisplayName -match "forefront identity"} | Stop-Service -Force
Get-Service | where {$_.DisplayName -match "Azure Active Directory sync"} | Set-Service -StartupType Disabled
Get-Service | where {$_.DisplayName -match "Azure Active Directory sync"} | Stop-Service -Force

Then on the new AD Connect server, click start > open Azure AD Connect.

2016-02-02_1101

Configure staging mode…

2016-02-02_1102

Enter Global Admin credentials for Office 365…

2016-02-02_1104

Un-check Enable Staging Mode….

2016-02-02_1111

That’s it…..

2016-02-02_1120

You will notice it says to enable the sync task in Windows Task Scheduler before it will work – do that.

Below here is some PowerShell of some handy little admin tasks in which you can benefit from.

Import-Module ADSync

# Get all ADSync cmdlets
Get-Command | Where-Object {$_.ModuleName -eq "ADSync"}

#region Force a Directory Sync
if((Get-ScheduledTask -TaskName "Azure AD Sync Scheduler").Actions.Execute -match "DirectorySyncClientCmd")
{Start-ScheduledTask -TaskName "Azure AD Sync Scheduler"}
#endregion

# To see if password sync is enabled
Get-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector.Name

#region Directory Sync

# To perform a full directory sync
Start-Process -FilePath "C:\Program Files\Microsoft Azure AD Sync\Bin\DirectorySyncClientCmd.exe" -ArgumentList Initial

# To perform a delta directory sync
Start-Process -FilePath "C:\Program Files\Microsoft Azure AD Sync\Bin\DirectorySyncClientCmd.exe" -ArgumentList Delta

#endregion

#region Trigger a Full Password Sync in Azure AD Sync
# Get All Connectors
$adConnector = Get-ADSyncConnector | % {$_.Type -eq "AD"}
$aadConnector = Get-ADSyncConnector | % {$_.Type -eq "Extensible2" -or $_.SubType -like "*Azure*"}

$c = Get-ADSyncConnector -Name $adConnector.Name
$p = New-Object Microsoft.IdentityManagement.PowerShell.ObjectModel.ConfigurationParameter "Microsoft.Synchronize.ForceFullPasswordSync", String, ConnectorGlobal, $null, $null, $null
$p.Value = 1
$c.GlobalParameters.Remove($p.Name)
$c.GlobalParameters.Add($p)
$c = Add-ADSyncConnector -Connector $c

Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector.Name -TargetConnector $aadConnector.Name -Enable $false
Set-ADSyncAADPasswordSyncConfiguration -SourceConnector $adConnector.Name -TargetConnector $aadConnector.Name -Enable $true
#endregion

# Get the latest event of the password change
(Get-EventLog -LogName Application | where {$_.EventID -eq "656"} | select -First 1).Message
(Get-EventLog -LogName Application | where {$_.EventID -eq "656"} | select -First 1).Message

Surface Pro waking up from sleep hibernation – Windows 10

I used to have this problem “My Surface Pro wakes up from sleep / hibernation in my bag and gets hot and the battery goes flat.

It seems that this is caused by the keyboard and mouse having a Power Management setting “Allow this device to wake up this computer“. Why would you have this option for these peripherals especially with a Surface Pro for example when the keyboard and mouse is a little flexible and can easily be knocked and activated in a bag.

I must say that this has worked for me and solved my issue of the random wake ups in the bag on more than one occasion. Also too, I have noticed that on some occurrences Microsoft would release an update and as a result it would set these devices back to their default settings.

So here are the devices in question: Right click on the Start Button > click Device Manager. Only some keyboard and mouse peripherals shown here will allow for the option to wake up the computer.

devices

And here is one of the properties in question “Allow this device to wake up this computer“.

Device

To fix this – rather then going through each and every device, there’s an easy way, run two commands. Open command prompt – click start > type in ‘cmd‘ > hit CTRL-SHIFT-ENTER > Yes if it prompts you….

cmd-prompt

In command prompt type in powercfg -devicequery wake_armed to find devices which are set to wake the computer. The simply type in powercfg -devicedisablewake “<device_name>” to disable the devices listed one-by-one.

Windows 10 upgrade issues and problems

People at work had issues with the Windows 10 upgrade after upgrading from Windows 8.1 Update. This guy’s Surface Pro 3 Windows 10 display and keyboard wasn’t working properly especially when plugging in an external monitor, the Display Port didn’t work at all. Also when running Windows Update he was getting error code 0x80248007 when using Windows Updates.

Only way to fix this was to re-build from scratch, a nice clean install of Windows 10 and it doesn’t take long.

Download a full version of Windows 10 here https://www.microsoft.com/en-us/software-download/windows10 you need at least 3GB on a USB key, this tool will write the full Windows 10 install media to a USB key and makes it bootable. You can do this on any device.

Then boot up/start up the Surface Pro from the USB key as per this article http://www.microsoft.com/surface/en-au/support/storage-files-and-folders/boot-surface-from-usb-recovery-device?os=windows-10

As for activation, as Windows 10 activation is now tied to the device, as Windows 10 was already installed and activated previously, activation works seamlessly in the background.

AzureCon Announcements

There were several announcements from AzureCon last week. Here are some of the highlights.

New N family of Azure Virtual Machines. These have the latest flagship NVIDIA Tesla GPU, the K80, superfast RDMA network & high-end remote virtualisation. Azure becomes the first hyperscale cloud provider to bring the capabilities of NVidia’s Quadro High End Graphics Support to the cloud.

Azure Compute Pre-Purchase Plan. Pre-purchase compute hours for one year and save up to 63% on compute costs. This plan will be available globally starting December 1. https://azure.microsoft.com/en-us/blog/building-the-intelligent-cloud-announcing-new-azure-innovations-to-transform-business/

Announcing general availability of ExpressRoute for Office 365. Everyone knows that Office 365 is hosted in Azure. If you have an ExpressRoute connection in Azure right now, the Office 365 component is a separate peer/route in which all Office 365 related traffic is routed over the ExpressRoute connection. A bit of background information here, with ExpressRoute, it’s all about peerings, which are similar to network routes. Private peering is used by Azure Compute and cloud services and is considered a trusted extension of on-premises core network into Azure. Public Peering is used by services like Azure Storage, SQL databases and websites that need public IPs and the last one Microsoft peering is Office 365.

  • Depending on how you are setup with ExpressRoute. If you have 802.1ad setup on your network, a single service key (single dedicated circuit) can be used to create the virtual circuit pair (primary and secondary VC) which can be used for both Private, Public and Microsoft peering. Different C-tags within the 802.1ad tag can be used to separate the private, public and Microsoft peering sessions using the same pair of VCs.
  • If you have 802.1Q (Dot1Q) setup on your network, you are required to order two separate dedicated circuits on Azure; each with a unique Service key. One service key for a pair of virtual circuits (primary and secondary VC) for private peering, another service key for a pair of virtual circuits (primary and secondary VC) for public peering and another service key for a pair of virtual circuits (primary and secondary VC) for Microsoft (Office 365) peering.
  • Microsoft Azure always uses 802.1ad (QinQ) on their side.

New DV2 virtual machines in Azure, Haswell processors, 35% faster than the D series http://screencast.com/t/s5395tz819. You can attached up to 32x 500 IOPS disks each at 1024 MBs in size.

A 5 TB SMB3 file server in the cloud, Azure Files now accessible outside the Azure data centre. Simply map a drive to it. You can map a drive using PowerShell:

Create a new Azure Files share:

# Get Storage account key
$StorageAccount = "<storage_account>"
$StorageKey = (Get-AzureStorageKey -StorageAccountName $StorageAccount).Primary
$context = New-AzureStorageContext -StorageAccountName $StorageAccount -StorageAccountKey $StorageKey

# create a new share
$s = New-AzureStorageShare contributor -Context $context
$s = New-AzureStorageShare reader -Context $context

# create a directory in the test share just created
New-AzureStorageDirectory -Share $s -Path contribute
New-AzureStorageDirectory -Share $s -Path read

Map a drive to it:

$net1 = new-object -ComObject WScript.Network
$net2 = new-object -ComObject Shell.Application
$net1.MapNetworkDrive("m:", "\\<storage_account>.file.core.windows.net\sharename", $true, "<storage_account>", "<storage_key>")
$net2.NameSpace('m:').Self.Name = "My Share"

Most significant advantage for SMB3? Encryped connections using AES-CCM [RFC5084] as the encryption algorithm.

3 new Azure regions now GA online, India Central / India South / India West. This takes the total of Azure regions to 20 https://azure.microsoft.com/en-in/regions/ and not 22 as what was told to us at the conference, they are jumping the gun, there are two new Azure regions announced to be opened for business next year 2016 in Canada – Toronto and Quebec City.

Use PowerShell to make Rest API calls using JSON & OAuth

If you come from an IT Pro background like me, I have probably scared you off already by mentioning terms like Rest API, RegEx, JSON & OAuth. But don’t worry, I am going to walk you though some examples using PowerShell to automatically capture data from a random websites and then in turn post Google blogger blogs including the captured data and send Twitter tweets of the blogs URL using PowerShell.

Why is this important? Think of how large the world wide web is. Imagine a website or group of websites you want to monitor and capture data from. Could be internal websites or external websites. Could be weather, sports results, stock market results etc. Either way, I will show you how to scan the internet, filter and massage the data and then blog and tweet it. The best part of all, this is fully automatic.

In a nutshell, OAuth authentication is made up of different stages. First stage is to get a Client ID & Client Secret, these two fields are available to you when you setup your ‘app’ in either Twitter or Google. Then on top of these, you need an access token and this access token is used when trying to access your app to make it do things, like post a blog or tweet a tweet. Getting the access token in Google attracts more steps than that of Twitter. The access token is the golden key in which you need to do things with e.g . post blogs. With Google, there’s a couple of other steps prior in which you need to get an authorization code and then exchange this authorization code for both an access token and refresh token. The refresh token, if kept, can be used later on to get a new access token each time without going through the other two steps. Roughly every hour you need a new access token, so using the refresh token is a much easier process. Also using an automation process like a robot to do the work or automated task, by using a refresh token it doesn’t require human intervention in order to obtain the access token.

Below are the steps for the Google side of things and is an overview to what is in the script:

  1. Get the authorization code, you provide the following pieces of information in the form of a URL. What is returned is an authorization code which is embedded in the URL. What I mean by this, when running the below parameters compiled into a single URL, it navigates to the URL specified in the redirect_url and a code is appended to the URL. This is why for this step, Internet Explorer is used.Please note, this step is only required if a refresh token is not already obtained or kept.
    1. $scope = “https://www.googleapis.com/auth/blogger&#8221;
    2. $response_type = “code”
    3. $approval_prompt = “force”
    4. $access_type = “offline”
    5. $redirect_uri = “https://domain.com&#8221;
  2. Exchange the authorization code for a refresh token and access token.Please note, this step is only required if a refresh token is not already obtained or kept.
    1. $grantType = “authorization_code”
    2. $requestUri = “https://accounts.google.com/o/oauth2/token&#8221;
    3. $requestBody = “code=$authorizationCode&client_id=$app_key&client_secret=$app_secret&grant_type=$grantType&redirect_uri=$redirect_uri”
    4. Invoke-RestMethod -Method Post -Uri $requestUri -ContentType “application/x-www-form-urlencoded” -Body $requestBody
  3. Exchange the refresh token for an access token. As long as the refresh token is kept, then this step is all that is needed to gain new access tokens
    1. $grantType = “refresh_token”
    2. $requestUri = “https://accounts.google.com/o/oauth2/token&#8221;
    3. $requestBody = “refresh_token=$refreshToken&client_id=$app_key&client_secret=$app_secret&grant_type=$grantType”
    4. Invoke-RestMethod -Method Post -Uri $requestUri -ContentType “application/x-www-form-urlencoded” -Body $requestBody

image

Twitter setup

First off, you’re going to need to set yourself up a Twitter app, go to Twitter’s application page, sign in….

image

Click on Create new app.

image

Fill in the details. The Callback URL is not really important for this exercise.

image

Click on permissions

image

Confirm read and write is selected

Click on Keys and Access Tokens

image

At the bottom, click on Create my access token

image

Now on this page you need to make a note of Consumer Key (API Key), Consumer Secret (API Secret), Access Token and Access Token Secret. This is what you’ll need in the PowerShell script to automate tweets. Notice too that Twitter give you the access token then and there? Whereas in the Google world, the access token is not at freely available.

Google setup

For the Google setup, you’ll need to go to https://console.developers.google.com and sign-up/login and create a new project. The new project will give you access to an

image

Up the top, click on the drop down list and select your new project.

image

On the left, click on APIs, then select Blogger API, then select Enable API. This will enable the Blogger API for your new project.

image 

After the API is enabled, you need to setup the credentials. Click on credentials to the left > add credentials > select OAuth 2.0 client ID

image

Fill out the consent screen details as you wish. Then on the next page, select Web application, give it a name and fill in the redirect URI. Remember, the redirect URI doesn’t have to be anything custom or specific, just any HTTPS URL will do, but better to use your own HTTPS site if you have one.

image

Take a note of the client ID and client secret on the next screen, as you will need these in the PowerShell script at the top.

image 

The full script is below, a good example of capturing data, filtering it, massaging it and then reporting on it.

# Google Blogger necessities - Setup your app here https://console.developers.google.com/ along with OAuth credentials
$blogID = "This is the ID of your blog, a series of numbers found on the Blogger site"
$app_key = "This is your client ID from your Google app"
$app_secret = "This is your client secret from your Google app"
$redirect_uri = "https://domain.com" # Can be any HTTPS URL, URL has to be specified when adding the OAuth credentials to your app here https://console.developers.google.com/

# Twitter necessities - http://www.adamtheautomator.com/twitter-module-powershell/
$TwitterApiKey = 'n924hf924f92f0824f'
$TwitterApiSecret = 'ng3op8g39ghn39g4wn8gi3p'
$TwitterAccessToken = ‘n938hgv0985jg0398g059jg03g950jg'
$TwitterAccessTokenSecret = 'jv30498g05jg498h3u04u533j9g05g839hg5893'

$TwitterMessage = "Songs played on the Australian Radio Network in the past hour: " # URL is automatically appended

<######################################################################################

### Start polling station websites for data on a loop
Then write hourly results to a new Google Blogger post and tweet the URL on Twitter

######################################################################################>

#region If no refresh token, get a Google refresh token now. Doing this early as human interaction may be required.

### If no refresh token - requires human interaction with IE
if(!($refreshToken)){

### Get Google API access - https://developers.google.com/identity/protocols/OAuth2WebServer#offline
$scope = "https://www.googleapis.com/auth/blogger"
$response_type = "code"
$approval_prompt = "force"
$access_type = "offline"

### Get the authorization code
$auth_string = "https://accounts.google.com/o/oauth2/auth?scope=$scope&response_type=$response_type&redirect_uri=$redirect_uri&client_id=$app_key&access_type=$access_type&approval_prompt=$approval_prompt"

$ie = New-Object -comObject InternetExplorer.Application
if($approval_prompt -eq "force"){$ie.visible = $true}
$ie.navigate($auth_string)
#Wait for user interaction in IE, manual approval
do{Start-Sleep 1}until($ie.LocationURL -match 'code=([^&]*)')
$null = $ie.LocationURL -match 'code=([^&]*)'
$authorizationCode = $matches[1]
$ie.Quit()

### exchange the authorization code for a refresh token and access token
$grantType = "authorization_code"
$requestUri = "https://accounts.google.com/o/oauth2/token"
$requestBody = "code=$authorizationCode&client_id=$app_key&client_secret=$app_secret&grant_type=$grantType&redirect_uri=$redirect_uri"

$response = Invoke-RestMethod -Method Post -Uri $requestUri -ContentType "application/x-www-form-urlencoded" -Body $requestBody

$refreshToken = $response.refresh_token
}

#endregion

#region Clear the variables and setup the variable arrays for the data to be captured to

$973fmresult = @()

#endregion

#region Capture app radio statons on a approx 1 min loop

while ($true){
$freshpopsonglist = @()

#region Capture data phase
cls
#973 FM - Brisbane
$matches = @()
Start-Sleep -Seconds 1
Write-Host -NoNewline "Now playing on 973 FM" -ForegroundColor Cyan;Write-Host " Brisbane" -ForegroundColor Yellow
$uri = (Invoke-WebRequest 'http://media.arn.com.au/xml/973_now.xml').content
Start-Sleep -Seconds 1
$null = $uri -match '<now_playing>(.|\n)*artist><!\[CDATA\[([^]]*)\]\]></artist(.|\n)*</now_playing>';$artist = $matches[2]
$null = $uri -match '<now_playing>(.|\n)*title\sgeneric="False"><!\[CDATA\[([^]]*)\]\]></title(.|\n)*</now_playing>';$title = $matches[2]
$973fmsong = "{0} - {1}" -f $artist, $title
if((!($973fmresult -eq $973fmsong)) -and `
($973fmsong -notmatch "97.3fm") -and `
($973fmsong -match "..\s-\s..") -and `
($973fmsong -notmatch "Audio Type Changed")){
$973fmresult = $973fmsong
$freshpopsonglist += $973fmsong -replace("[\r|\n]")
}
Write-Host $973fmsong`n

#endregion

#Write all POP song values to other variable
$capturehourly += ($freshpopsonglist | select -Unique) -replace("`n")

#region Send hourly songs to Blogger
if(((Get-Date).Minute -eq "58") -or ((Get-Date).Minute -eq "59") -and (($capturehourly | measure).count -gt "10")){

#region Google Blogger API

##################################################################################
### If no refresh token - requires human interaction with IE
if(!($refreshToken)){

### Get Google API access - https://developers.google.com/identity/protocols/OAuth2WebServer#offline
$scope = "https://www.googleapis.com/auth/blogger"
$response_type = "code"
$approval_prompt = "force"
$access_type = "offline"

### Get the authorization code
$auth_string = "https://accounts.google.com/o/oauth2/auth?scope=$scope&response_type=$response_type&redirect_uri=$redirect_uri&client_id=$app_key&access_type=$access_type&approval_prompt=$approval_prompt"

$ie = New-Object -comObject InternetExplorer.Application
if($approval_prompt -eq "force"){$ie.visible = $true}
$ie.navigate($auth_string)
#Wait for user interaction in IE, manual approval
do{Start-Sleep 1}until($ie.LocationURL -match 'code=([^&]*)')
$null = $ie.LocationURL -match 'code=([^&]*)'
$authorizationCode = $matches[1]
$ie.Quit()

### exchange the authorization code for a refresh token and access token
$grantType = "authorization_code"
$requestUri = "https://accounts.google.com/o/oauth2/token"
$requestBody = "code=$authorizationCode&client_id=$app_key&client_secret=$app_secret&grant_type=$grantType&redirect_uri=$redirect_uri"

$response = Invoke-RestMethod -Method Post -Uri $requestUri -ContentType "application/x-www-form-urlencoded" -Body $requestBody

$accessToken = $response.access_token
$refreshToken = $response.refresh_token
}

##################################################################################
### If refresh token exists
else{

### exchange the refresh token for an access token
$grantType = "refresh_token"
$requestUri = "https://accounts.google.com/o/oauth2/token"
$requestBody = "refresh_token=$refreshToken&client_id=$app_key&client_secret=$app_secret&grant_type=$grantType"

$response = Invoke-RestMethod -Method Post -Uri $requestUri -ContentType "application/x-www-form-urlencoded" -Body $requestBody

$accessToken = $response.access_token
}

##################################################################################

### Blogger API: Using the API - https://developers.google.com/blogger/docs/3.0/using#AddingAPost
$blogTitle = "ARN-PlayList-" + (Get-Date -format "yyyyMMdd-HHmm")
$songmax = ($capturehourly | Measure-Object).Count
$content = (Get-Date -format g) + "<br><br>Songs played on Brisbane's 973 FM in the past hour. Stations monitored:<br><br>973 FM - Brisbane<br><br>" + (@($capturehourly[0..$songmax]) -join('<br>'))
##################### - http://blogs.technet.com/b/heyscriptingguy/archive/2012/10/08/use-powershell-to-convert-to-or-from-json.aspx
$body = @{
 kind = "blogger#post"
 blog = "id=$blogID"
 title = $blogTitle
 content = $content
}
$json = $body | ConvertTo-Json
####################
$blogurl = $null
$uri = "https://www.googleapis.com/blogger/v3/blogs/4370003358460014533/posts/"
$ContentType = "application/json"
$postblog = Invoke-RestMethod -Method Post -Uri $uri -Body $json -ContentType $ContentType -Headers @{"Authorization"="Bearer $accessToken"}
$blogurl = $postblog.url

<# Left in hear for troubleshooting, this is a good way of producing a nice clean output with good information.
Try {
 Invoke-RestMethod -Method Post -Uri $uri -Body $json -ContentType $ContentType -Headers @{"Authorization"="Bearer $accessToken"}
}
Catch {
 Write-Host $_.Exception.ToString()
 $error[0] | Format-List -Force
}
#>
#endregion

Start-Sleep -Seconds 5

#region Send hourly songs link to Twitter
$message = $TwitterMessage + $blogurl
$Body = "status=$Message"
$HttpEndPoint = "https://api.twitter.com/1.1/statuses/update.json"
$AuthorizationString = Get-OAuthAuthorization -TweetMessage $Message -HttpEndPoint $HttpEndPoint
Invoke-RestMethod -URI $HttpEndPoint -Method Post -Body $Body -Headers @{ 'Authorization' = $AuthorizationString } -ContentType "application/x-www-form-urlencoded"
#endregion

$freshpopsonglist = @()
$capturehourly = @()
}
#endregion

Start-Sleep -Seconds 20

}

#endregion

function Get-OAuthAuthorization {
 <#
 .SYNOPSIS
 This function is used to setup all the appropriate security stuff needed to issue
 API calls against Twitter's API. It has been tested with v1.1 of the API. It currently
 includes support only for sending tweets from a single user account and to send DMs from
 a single user account.
 .EXAMPLE
 Get-OAuthAuthorization -DmMessage 'hello' -HttpEndPoint 'https://api.twitter.com/1.1/direct_messages/new.json' -Username adam
 
 This example gets the authorization string needed in the HTTP POST method to send a direct
 message with the text 'hello' to the user 'adam'.
 .EXAMPLE
 Get-OAuthAuthorization -TweetMessage 'hello' -HttpEndPoint 'https://api.twitter.com/1.1/statuses/update.json'
 
 This example gets the authorization string needed in the HTTP POST method to send out a tweet.
 .PARAMETER HttpEndPoint
 This is the URI that you must use to issue calls to the API.
 .PARAMETER TweetMessage
 Use this parameter if you're sending a tweet. This is the tweet's text.
 .PARAMETER DmMessage
 If you're sending a DM to someone, this is the DM's text.
 .PARAMETER Username
 If you're sending a DM to someone, this is the username you'll be sending to.
 .PARAMETER ApiKey
 The API key for the Twitter application you previously setup.
 .PARAMETER ApiSecret
 The API secret key for the Twitter application you previously setup.
 .PARAMETER AccessToken
 The access token that you generated within your Twitter application.
 .PARAMETER
 The access token secret that you generated within your Twitter application.
 #>
 [CmdletBinding(DefaultParameterSetName = 'None')]
 [OutputType('System.Management.Automation.PSCustomObject')]
 param (
 [Parameter(Mandatory)]
 [string]$HttpEndPoint,
 [Parameter(Mandatory, ParameterSetName = 'NewTweet')]
 [string]$TweetMessage,
 [Parameter(Mandatory, ParameterSetName = 'DM')]
 [string]$DmMessage,
 [Parameter(Mandatory, ParameterSetName = 'DM')]
 [string]$Username,
 [Parameter()]
 [string]$ApiKey = $TwitterApiKey,
 [Parameter()]
 [string]$ApiSecret = $TwitterApiSecret,
 [Parameter()]
 [string]$AccessToken = $TwitterAccessToken,
 [Parameter()]
 [string]$AccessTokenSecret = $TwitterAccessTokenSecret
 )
 
 begin {
 $ErrorActionPreference = [System.Management.Automation.ActionPreference]::Stop
 Set-StrictMode -Version Latest
 try {
 [Reflection.Assembly]::LoadWithPartialName("System.Security") | Out-Null
 [Reflection.Assembly]::LoadWithPartialName("System.Net") | Out-Null
 } catch {
 Write-Error $_.Exception.Message
 }
 }
 
 process {
 try {
 ## Generate a random 32-byte string. I'm using the current time (in seconds) and appending 5 chars to the end to get to 32 bytes
 ## Base64 allows for an '=' but Twitter does not. If this is found, replace it with some alphanumeric character
 $OauthNonce = [System.Convert]::ToBase64String(([System.Text.Encoding]::ASCII.GetBytes("$([System.DateTime]::Now.Ticks.ToString())12345"))).Replace('=', 'g')
 Write-Verbose "Generated Oauth none string '$OauthNonce'"
 
 ## Find the total seconds since 1/1/1970 (epoch time)
 $EpochTimeNow = [System.DateTime]::UtcNow - [System.DateTime]::ParseExact("01/01/1970", "dd/MM/yyyy", $null)
 Write-Verbose "Generated epoch time '$EpochTimeNow'"
 $OauthTimestamp = [System.Convert]::ToInt64($EpochTimeNow.TotalSeconds).ToString();
 Write-Verbose "Generated Oauth timestamp '$OauthTimestamp'"
 
 ## Build the signature
 $SignatureBase = "$([System.Uri]::EscapeDataString($HttpEndPoint))&"
 $SignatureParams = @{
 'oauth_consumer_key' = $ApiKey;
 'oauth_nonce' = $OauthNonce;
 'oauth_signature_method' = 'HMAC-SHA1';
 'oauth_timestamp' = $OauthTimestamp;
 'oauth_token' = $AccessToken;
 'oauth_version' = '1.0';
 }
 if ($TweetMessage) {
 $SignatureParams.status = $TweetMessage
 } elseif ($DmMessage) {
 $SignatureParams.screen_name = $Username
 $SignatureParams.text = $DmMessage
 }
 
 ## Create a string called $SignatureBase that joins all URL encoded 'Key=Value' elements with a &
 ## Remove the URL encoded & at the end and prepend the necessary 'POST&' verb to the front
 $SignatureParams.GetEnumerator() | sort name | foreach { 
 Write-Verbose "Adding '$([System.Uri]::EscapeDataString(`"$($_.Key)=$($_.Value)&`"))' to signature string"
 $SignatureBase += [System.Uri]::EscapeDataString("$($_.Key)=$($_.Value)&".Replace(',','%2C').Replace('!','%21'))
 }
 $SignatureBase = $SignatureBase.TrimEnd('%26')
 $SignatureBase = 'POST&' + $SignatureBase
 Write-Verbose "Base signature generated '$SignatureBase'"
 
 ## Create the hashed string from the base signature
 $SignatureKey = [System.Uri]::EscapeDataString($ApiSecret) + "&" + [System.Uri]::EscapeDataString($AccessTokenSecret);
 
 $hmacsha1 = new-object System.Security.Cryptography.HMACSHA1;
 $hmacsha1.Key = [System.Text.Encoding]::ASCII.GetBytes($SignatureKey);
 $OauthSignature = [System.Convert]::ToBase64String($hmacsha1.ComputeHash([System.Text.Encoding]::ASCII.GetBytes($SignatureBase)));
 Write-Verbose "Using signature '$OauthSignature'"
 
 ## Build the authorization headers using most of the signature headers elements. This is joining all of the 'Key=Value' elements again
 ## and only URL encoding the Values this time while including non-URL encoded double quotes around each value
 $AuthorizationParams = $SignatureParams
 $AuthorizationParams.Add('oauth_signature', $OauthSignature)
 
 ## Remove any API call-specific params from the authorization params
 $AuthorizationParams.Remove('status')
 $AuthorizationParams.Remove('text')
 $AuthorizationParams.Remove('screen_name')
 
 $AuthorizationString = 'OAuth '
 $AuthorizationParams.GetEnumerator() | sort name | foreach { $AuthorizationString += $_.Key + '="' + [System.Uri]::EscapeDataString($_.Value) + '", ' }
 $AuthorizationString = $AuthorizationString.TrimEnd(', ')
 Write-Verbose "Using authorization string '$AuthorizationString'"
 
 $AuthorizationString
 
 } catch {
 Write-Error $_.Exception.Message
 }
 }
}