Wednesday 26 August 2015

Palo Alto UID Agent with NPS RADIUS SSO

Don't get me wrong. I love my Palo Alto Firewall. Best decision i have ever made to be purchased, its a brilliant device, but the SSO functionality when it comes to RADIUS is a bit shit because well it isn't there.

The scenario I will give you is if you have NPS(RADIUS) running for your wireless and you would like SSO authenticated internet for you non-domain users, ie BYOD, then you are shit out of luck with Palo Alto, it will prompt the user for a login. Not the end of the world, but still annoying.

Thus this amazing script came which watches the NPS log file, comparing it to the DHCP leases, and will send and xml request through the Palo Alto UID(download UID with your Palo Alto login at link).

It does create a little bit of load on the server and I think it could do with some refining to allow it to run a lot faster. If I ever get around to it I will make a multi threaded version allowing multiple subnets to be scanned at the same time and also do some subnet mapping based off the SSID of the wireless to reduce the load of requesting subnets from the DHCP which appears to be the most CPU intensive and the part that slows the script down the most.

There are two prereqs with 
- Remote DHCP Admin Console, which adds the Powershell Plugins used in the script
- Palo Alto UID Agent with the following turned on

and here is the script
#original script from https://github.com/cesanetwan/uid-radius-script-ps
# modified to remove config file to reduce load as well as fixed a few errors in the code
param([string]$global:strEventUser, [string]$global:strCallingStation)
$ErrorActionPreference = "Stop"
$global:strVersion = "5.8ps"
#create arrays - you can leave them as is
$global:aClientIPs = @()
$global:aExclusions = @()
$global:aDHCPServers = @()
#domain name
$global:strDomain = "DOMAIN"
#location of your NPS Log file
$global:strLogPath = "C:\System32\LogFiles\"
#IP Address of the Palo Alto UID Script
#Runs best if it is installed on the same server as NPS
$global:strAgentServer = "127.0.0.1"
$global:strAgentPort = "5006"
#subnets to exclude from the DHCP Request
$global:aExclusions += ""
#leave this as DHCP, its now the only option that works
$global:strLogFormat = "DHCP"
#DHCP Servers, if you set them up for resiliance you shuld only need one here
$global:aDHCPServers += "PDC"
$global:aDHCPServers += "DC"
#settings for the UID Agent, leave them as is
$global:strVsys = "vsys2"
$global:strAPIKey = "key"
$global:blnAgent = "1"
$global:strTimeout = "120"
#debug to create a log file in the following C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log
$global:strDebug = "0"
#the address of your Palo Alto for the API
$global:strPostAddr = "https://192.168.1.1/api/"
#do you have a proxy server set in the UID Agent?
$global:strProxy = "0"
#do you want it to run multiple passes? if you are running one DHCP you can leave this as 0
$global:blnMultipass = "0"
Function PostToAgent
{
param([string]$strUserAgentData)
If ([int]$global:blnAgent -eq 1)
{
$url = "https://" + $global:strAgentServer + ":" + $global:strAgentPort + "/"
[System.Net.HttpWebRequest]$request = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($url)
$request.Method = "PUT"
If ([int]$global:strDebug -gt 0)
{
$message = "Local agent installed, posting data to " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
If ($global:strProxy -eq "1")
{
       $url = $global:strPostAddr
If ([int]$global:strDebug -gt 0)
{
$message = "Posting to XMLAPIProxy, URL: " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
   $url = $global:strPostAddr + "?key=" + $global:strAPIKey + "&type=user-id&action=set&vsys=" + $global:strVsys + "&client=wget&file-name=UID.xml"
If ([int]$global:strDebug -gt 0)
{
$message = "Posting to XMLAPI on firewall, URL: " + $url
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
[System.Net.HttpWebRequest]$request = [System.Net.HttpWebRequest] [System.Net.WebRequest]::Create($url)
$request.Method = "POST"
}
If ([int]$global:strDebug -gt 0)
{
$message = "Starting post"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$request.ContentType = "text/xml"
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = {$true}
$bytes = [System.Text.Encoding]::UTF8.GetBytes($strUserAgentData)
$request.ContentLength = $bytes.Length
[System.IO.Stream] $outputStream = [System.IO.Stream]$request.GetRequestStream()
        $outputStream.Write($bytes,0,$bytes.Length)
        $outputStream.Close()
If ([int]$global:strDebug -gt 0)
{
$message = "Finished Post"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
try
    {
        [System.Net.HttpWebResponse]$response = [System.Net.HttpWebResponse]$request.GetResponse()  
        $sr = New-Object System.IO.StreamReader($response.GetResponseStream())    
        $txt = $sr.ReadToEnd()
If ([int]$global:strDebug -gt 0)
{
$message = "Response: " + $txt
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
    }
    catch [Net.WebException] {
        [System.Net.HttpWebResponse] $resp = [System.Net.HttpWebResponse] $_.Exception.Response
        add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uidradiuserrors.log" -Value $resp -Force
    }
}
Function CleanMac
{
param([string]$strMac)
$strMac = $strMac -replace "-", ""
        $strMac = $strMac -replace "\.", ""
        $strMac = $strMac -replace ":", ""
        $strMac = $strMac.ToLower()
        return $strMac
}

Function ProcessDHCPClients
{
If ($global:strEventUser.contains("\"))
{
$pos = $global:strEventUser.IndexOf("\")
$global:strEventUser = $global:strEventUser.Substring($pos+1)
} ElseIf ($global:strEventUser.contains("@"))
{
$pos = $global:strEventUser.IndexOf("@")
$global:strEventUser = $global:strEventUser.Substring(0,$pos)
}
If (-Not ($global:strEventUser.contains("$")) -and -Not ($global:strEventUser.contains("host/")) )
{
If ($global:strCallingStation -match "\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b")
{
$aMatchedIPs = @()
$aMatchedIPs += $global:strCallingStation
If ([int]$global:strDebug -gt 0)
{
$message = "CallingStation is IP, no need for DHCP query"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
Else
{
If ($global:blnMultipass -eq "0") {
If ([int]$global:strDebug -gt 0)
{
$message = "No MultiPass required, performing single pass"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$aMatchedIPs = @()
foreach ($DHCPServer in $global:aDHCPServers)
{
If ([int]$global:strDebug -gt 0)
{
$message = "Querying DHCP Server: " + [string]$DHCPServer
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$scopes = Get-DhcpServerv4Scope -CN $DHCPServer | select ScopeId
foreach ($scope in $scopes)
                                        {
If ([int]$global:strDebug -gt 0)
{
$message = "Checking Scope: " + [string]$scope
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
                                               $aReservations = Get-DhcpServerv4Lease -CN $DHCPServer -ScopeId $scope.ScopeID -AllLeases | select IPAddress, ClientID
                                               foreach ($reservation in $aReservations)
                                               {
                                                    $MAC = CleanMac($reservation.ClientID)
   If ([int]$global:strDebug -gt 1)
   {
$message = $MAC
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
   }
                                                    $global:strCallingStation = CleanMac($global:strCallingStation)
                                                    If ($global:strCallingStation -eq $MAC)
                                                    {
If ([int]$global:strDebug -gt 0)
       {
   $message = "MAC found, IP is: " + [string]$reservation.IPAddress
   add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
       }
                                                        $aMatchedIPs += $reservation.IPAddress
                                                    }
                                                }
                                        }
}
}
Else
{
If ([int]$global:strDebug -gt 0)
{
$message = "MultiPass required, performing two passes"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$aMatchedIPs = @()
$mp = 0
While ($mp -lt 2) {
If ([int]$global:strDebug -gt 0)
{
$message = "Pass " + [string]$mp + ":"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
foreach ($DHCPServer in $global:aDHCPServers)
{
If ([int]$global:strDebug -gt 0)
{
$message = "Querying DHCP Server: " + [string]$DHCPServer
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
$scopes = Get-DhcpServerv4Scope -CN $DHCPServer | select ScopeId
foreach ($scope in $scopes)
                                        {
If ([int]$global:strDebug -gt 0)
{
$message = "Checking Scope: " + [string]$scope
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
                                                $aReservations = Get-DhcpServerv4Lease -ScopeId $scope.ScopeID -AllLeases | select IPAddress, ClientID
                                                foreach ($reservation in $aReservations)
                                                {
                                                    $MAC = CleanMac($reservation.ClientID)
If ([int]$global:strDebug -gt 1)
    {
$message = $MAC
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
    }
                                                    $global:strCallingStation = CleanMac($global:strCallingStation)
                                                    If ($global:strCallingStation -eq $MAC)
                                                    {
If ([int]$global:strDebug -gt 0)
        {
    $message = "MAC found, IP is: " + [string]$reservation.IPAddress
    add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
        }
                                                        $aMatchedIPs += $reservation.IPAddress
                                                    }
                                                }
                                        }
}
$mp = $mp + 1
}
}
}
foreach ($address in $aMatchedIPs)
{
If ($global:strProxy -eq "1")
{
[string]$strXMLLine = "<uid-message><version>1.0</version><scriptv>" + $global:strVersion + "</scriptv><type>update</type><payload><login>"
}
Else
{
[string]$strXMLLine = "<uid-message><version>1.0</version><type>update</type><payload><login>"
}
If ($global:blnAgent -eq "1")
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """/>"
}
Else
{
If ($global:strProxy -eq "1")
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """ timeout=""" + $global:strTimeout + """ vsys=""" + $global:strVsys + """/>"
}
Else
{
$strXMLLine = $strXMLLine + "<entry name=""" + $global:strDomain + "\" + $global:strEventUser + """ ip=""" + $address + """ timeout=""" + $global:strTimeout + """/>"
}
}
$strXMLLine = $strXMLLine + "</login></payload></uid-message>"
If ([int]$global:strDebug -gt 0)
{
$message = "Posting mapping: " + $strXMLLine
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
PostToAgent $strXMLLine
}
}
}
Try
{
If ([int]$global:strDebug -gt 0)
{
$ct = Get-Date
$message = "Script launched at " + [string]$ct + " with arguments " + $global:strEventUser + " & " + $global:strCallingStation
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "==========================================================================" -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "Config Loaded Successfully" -Force
}
If ($global:strLogFormat -eq "DTS")
{
#ProcessDTSLog
}
ElseIf ($global:strLogFormat -eq "IAS")
{
#ProcessIASLog
}
ElseIf ($global:strLogFormat -eq "DHCP")
{
If ([int]$global:strDebug -gt 0)
{
$message = "Script in DHCP mode, starting DHCP Process"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
ProcessDHCPClients
If ([int]$global:strDebug -gt 0)
{
$message = "Finished processing DHCP Clients"
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
}
}
If ([int]$global:strDebug -gt 0)
{
$ct = Get-Date
$message = "Script finished at: " + [string]$ct
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value $message -Force
add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uiddebug.log" -Value "==========================================================================" -Force
}
}
Catch
{
    $ErrorMessage = $_.Exception.Message
    $FailedItem = $_.Exception.ItemName
    $ErrorLog = $FailedItem + " failed with message " + $ErrorMessage
    add-content -Path "C:\Program Files (x86)\Palo Alto Networks\User-ID Agent\uidradiuserrors.log" -Value $ErrorLog -Force
    Break
}

you will then need to create a task schedule which i have exported for you so you will need to change the script location and the user running

The original script was from https://github.com/cesanetwan/uid-radius-script-ps but it had quite a few errors in it that I have cleaned up and removed the config file from it as it was causing the script to slow down quite a lot. The next stage is to multi thread the sub net scanning to increase the rate at which the script can run and do mapping of subnets to wireless SSIDs coming through the NPS log file. I will most likely also remove all the debugging if statements and have a running/debug version.

anywho i thought it would be a good script to share and credit to the cesanetwan script for the base of this one.

Tuesday 25 August 2015

Reset User Passwords with AD Self Service Portal

I ran across a problem when we wanted specific users to be able to reset there passwords through some sort of self service mechanism.

After looking at many services(very expensive services!) to reset passwords which a lot of required enrolling, and where a pain to setup, which  I stumbled across the following page 

Although I was explicitly looking for a method of password reset via email I went about and modified the script to suit my needs.

so without further ado i present

function Create-RandomString()
{
  $aChars = @()
  $aChars = "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "A", "C", "b", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "2", "3", "4", "5", "6", "7", "8", "9", "_", ";"
  $intUpperLimit = Get-Random -minimum 8 -maximum 10

  $x = 0
  $strString = ""
  while ($x -lt $intUpperLimit)
  {
     $a = Get-Random -minimum 0 -maximum $aChars.getupperbound(0)
     $strString += $aChars[$a]
     $x += 1
  }

  return $strString
}

#prereqs need to be installed
# - Remote Admin tools for AD - RSAT
# - Exchange pluggins http://www.microsoft.com/en-us/download/confirmation.aspx?id=42951
#original script from
#http://deployhappiness.com/reset-user-passwords-with-ad-self-service-portal/
#password change script for changing the reset password
#https://unopasscore.codeplex.com/
#Configuration Block
#SMTP Server that mail will go OUT through
$SmtpServer = "smtp.email.com"
#email address that will be used as the REPLY email address
$ResetEmail = "donotreply@email.com"
#the email account to check
$Username = "PassReset@email.com"
$Password = "P@ASSWORD"
#exchange web address, this "should" work for any o365 install by changing the username and password above
$MailServer = "https://outlook.office365.com/ews/exchange.asmx"
$ExchangeVersion = "Exchange2013"
#AD Field that the recovery email address is stored in AD. The email recieved needs to come from the email address specified in the AD Field
$ADMailField = "mail"
#AD Server to send the password reset to. You can use the FQN or IP Address
$ADServer = "DC.local"

#User who shold recieve email notifications that a password was reset or an invalid request was sent.
$LoggingUser = "log@email.com"

#Download for file is here: http://www.microsoft.com/en-us/download/confirmation.aspx?id=42951
[Reflection.Assembly]::LoadFile("C:\Program Files\Microsoft\Exchange\Web Services\2.2\Microsoft.Exchange.WebServices.dll")

$email = New-Object Microsoft.Exchange.WebServices.Data.ExchangeService([Microsoft.Exchange.WebServices.Data.ExchangeVersion]::$ExchangeVersion)
$email.Credentials = New-Object Net.NetworkCredential($Username, $Password)
$uri=[system.URI] $MailServer
$email.Url = $uri
$inbox = [Microsoft.Exchange.WebServices.Data.Folder]::Bind($email,[Microsoft.Exchange.WebServices.Data.WellKnownFolderName]::Inbox)

if ($inbox.UnreadCount -gt 0) {
 $PropertySet = new-object Microsoft.Exchange.WebServices.Data.PropertySet([Microsoft.Exchange.WebServices.Data.BasePropertySet]::FirstClassProperties)
 $PropertySet.RequestedBodyType = [Microsoft.Exchange.WebServices.Data.BodyType]::Text;
  # Set search criteria - unread only
 $SearchForUnRead = New-object Microsoft.Exchange.WebServices.Data.SearchFilter+IsEqualTo([Microsoft.Exchange.WebServices.Data.EmailMessageSchema]::IsRead, $false)
 $items = $inbox.FindItems($SearchForUnRead,10)  #return only 10 unread mail items
  Import-Module -Name ActiveDirectory

 foreach ($item in $items.Items) {
   # load the property set to allow us to view the body
  $item.load($PropertySet)

  if($item.Body.text -Like "*") {
   $EmailAddress = $item.From.address
   $user = Get-ADUser -Filter {$ADMailField -eq $EmailAddress} -Properties $ADMailField -Server $ADServer
 
   If ($user -ne $null) {
    $PW = Create-RandomString
    if ($PW.length -gt 1) {
     Set-ADAccountPassword -identity $user.SamAccountName -Server $ADServer -Reset -NewPassword (ConvertTo-SecureString -AsPlainText $PW -Force)
     Unlock-ADAccount -identity $user.SamAccountName -Server $ADServer
   
     $PasswordAge = (Get-ADUser $user -Properties PasswordLastSet | Select PasswordLastSet)
     if ($PasswordAge.PasswordLastSet -ge (Get-Date).AddMinutes(-1)) {
     $Body = "Password reset for " + $user.SamAccountName + "-" + $user.DistinguishedName
     $UsernameReset = $user.SamAccountName
     send-mailmessage -to $LoggingUser -from $ResetEmail -subject "Password Reset - $PW" -body $Body -SmtpServer $SmtpServer
     send-mailmessage -to $item.From.address -from $ResetEmail -subject "Pass Login" -body "Your Username and Password are the following,`nUsername: $UsernameReset `nPassword: $PW`nTo change yout password please visit `n`nIf you are still having problems with your login please contact " -SmtpServer $SmtpServer
     }
    }
   }
   else {
    send-mailmessage -to $LoggingUser -from $ResetEmail -subject "Invalid email" -body "email address was not found " $item.From.address -SmtpServer $SmtpServer
    send-mailmessage -to $item.From.address -from $ResetEmail -subject "Invalid email " -body "Your email was not found." -SmtpServer $SmtpServer
   }
  }
  $item.Isread = $true
  $item.Update([Microsoft.Exchange.WebServices.Data.ConflictResolutionMode]::AlwaysOverwrite)
 }
}

The script works by doing the following,
- An email is sent from the account that cannot login to a specific account such as above PassReset@email.com, this is important that the email was sent FROM the account that has the associated email address you want to reset
- AD lookup is done filtering by the email address that it was received from.
- email sent back from a different address such as donotreply@email.com to prevent a mail loop from automatic replies with the username and password reset

This works off the concept that the email address that is sending the email is secure and as such is the authoritative source to find the account and provide details for.

I then used the following
link
to allow the user to change there password once they have logged in and that's it! we are all done.

EDIT:
I was also suggested this one which looks like it would work, over kill but would work.
https://github.com/jrivard/pwm

EDIT2:
I updated the script with a couple more variables to make it easier to configure
forgot to update the download the link

EDIT3: moved the function to the top

EDIT4: updated with a prereq

Thursday 5 July 2012

How to deploy Adobe CS6 Master Collection

This is actually surprisingly easy to do. I mean I have come from deploying CS4 to CS6 and I skipped all the editions between so I don't know if it got better at any point between these two but it is certainly better for CS6. Adobe is now offering the CS6 for digital download as well as the physical copies which are received. In the download section you will also have access to this awesome little app called "Application Manager Enterprise" :)

first thing to do is install the Application Manager Enterprise through the Adobe Licensing Website linky: https://licensing.adobe.com and of course you will need to have a valid adobe CS license to do
 this.

*EDIT*
A new version of AAMEE is now available and can be downloaded directly without logging in now 
from there blog here and a directly link to download AAMEE is here . That blog also contains doco with a lot of in-depth deployment tips if you want to have a look.


Install AME and then you will get this awesome shortcut in your start menu under adobe, yes i know it doesn't make a desktop shortcut unless they have updated it since I installed it :p

gee... i think we might select the "Create Install Package" :P
Obviously we can also create update packages and also alter existing package settings


Also pretty self explanatory, put the paths and package name you would like for the Adobe install










You can enter the serial number, or continue to build the package without a SN and have to enter it later
 now I feel silly because this really is so easy, select what you do or do not want to install and click next :p
This is where you get to select some options about the install like removing the EULA prompt and ect
The "Ignore conflicts and continue with installation" is especially handy if your computers already have the adobe reader installed when you want to install the CS6 pack. This will actually force the installer to simply upgrade the adobe reader to what ever it requires to run the rest of the pack contrary to simply ignoring the conflicts, it fixes them :)

This i am particularly impressed with. It will roll any updates that are avalaibel for CS6 install you install pack meaning its still only a single install to deploy.

Pretty nifty i reakon
Select the build button once you have selected the updates to roll in to the install.
The pack that is building, can take a while and seems to be hard drive and ram intensive, not so CPU intensive.
 its finished building. yep that's right that's all you need to do to build the CS pack.
Honestly it actually feels like it should be harder.
now you can browse to where so selected the installer to save to which in my case was
D:\AdobeCS6FullInstall\
There will then be a folder name of the what you called the installer ie D:\AdobeCS6FullInstall\AdobeCS6FullInstall1\
and within this there will be two folders and a file. The folder you want is the "Build" folder which will contain all of the setup files including the MSI file. The "Exceptions" folder contains a whole bundle of dependency installs which may be required by the installer.

Now that you have the MSI you can choose your favorite deployment system and off you go!
use the command
msiexec /i "AdobeCS6.msi" /qb         substitute what ever your msi is named into the command
to install the software silently onto the computer.

all up that's about it, the package with everything in it is roughly 6.5 to 7 gig so it is a large package to deploy probably not suitable for  wireless based deployment.

Monday 7 May 2012

How to redirect a specific browser in IIS7

Web services is one of those funny things that once it is setup, you really can simply leave it running for eternity and it will just keep on working, besides the regular vulnerability patch, but that will almost never require you to change the config. Even if you do need to add some form of webpage to IIS(Internet Information Services) it is very easy to configure and gone are the days of fiddling for endless hours to make something, which should be so easy, actually work properly.


Having said all this, I found that trying to redirect specific browsers to specific pages to be a some what painful experience when it came to IIS7. It wasnt a "hard" process, but the syntax for it can be some what .... Annoying.

It all started because we have users occasionally ring us up claiming they cannot install printers. We have now been, for a number of years, using the Microsoft IPP webpage solution to allow our users to install printers which works very well but here is the kicker. It only works properly in internet explorer. I don't care what anyone says or what the technet says. It simply works perfectly in IE, nothing else.
So how do I go about trying to force my users who have already been told 1000 times they need to use IE and even with a note at the top of the webpage still ring up asking why it wont work... We force a page up telling them to open Internet Explorer of course :) and thus the saga begins


To get started make sure you have at lease IIS7(no this will NOT work on IIS6, upgrade you slacko) and install the URL Rewriter module from Microsoft.

When you open your IIS Manager you will now get a pretty URL Rewriter icon, yay!!




Open the URL Rewriter and select "Add Rules" in the top right corner.

Select Black Rule and press OK








Match URL
Name: IE Allow
Using: Wildcards
Pattern: *
This means anything that hits this webserver will have this rule apply since the pattern we are searching for is * and wildcards apply, it applies to everything :)



Conditions
Select Add
Condition Input: {HTTP_USER_AGENT}
Pattern: *MSIE*
It will search the User Agent to see if it contains MSIE in it, since basically no third party browser ever wants to claim to be IE this works pretty well.














 Action
Action Type: Redirect
Redirect URL: www.google.com
This will redirect the webpage if the conditions are true, ie that Internet Explorer is used which will contain the MSIE in the User Agent. Google is just a place holder, you can forward it to what ever url you want, such as a page telling them to use firefox or chrome instead of IE :)

This rule will now redirect IE to a different URL but all the other browsers will simply pass through to the webpage since the rule does not apply to them. Since the most common theme is to actually block IE, rather than force it as I have done.(I hope your happy, you know who I am talking to person that complained about the doco I originally typed!) Obviously with this you can basically get any combo of browser to connect or not connect to your website all you will need to do is change the User Agent strings it is searching for. A massive list of User Strings for basically any browser can be found here so now you can make weird and wonderful rules to your hearts content!

Here is a list of website that helped me along the way in getting this working
There are all posts/blogs that helped me understand the formatting, I actually did it through the XML, then worked out you can do it through the UI easily as well.
- http://www.58bits.com/blog/2009/07/11/how-to-ban-internet-explorer-6-from-your-site-using-the-iis7-url-rewrite-module/
- http://forums.iis.net/t/1169853.aspx
- http://blogs.msdn.com/b/ie/archive/2010/03/23/introducing-ie9-s-user-agent-string.aspx
- http://forums.iis.net/p/1186609/2012883.aspx
The Microsoft doco on using URL Rewriter. its actually pretty good and easy to read
- http://learn.iis.net/page.aspx/734/url-rewrite-module/


Tuesday 3 April 2012

Tick box HTA in a SCCM Task Sequence?

I really like SCCM, System Center Configuration Manager. I think its pretty awesome and I mean we are still only using the 2007 version since, well we are still following the golden rule of waiting for SP1 to come out and/or for Windows 8 to force us to SCCM 2012 but I guess we will cross that bridge when we come to it.

The SCCM with all its goodness, can be a pain in the ass. It is incredibly flexible with the use of Task Sequences for deploying an OS and I believe that we have taken this to the most extreme point possible with SCCM. We are imaging our machines with only 2 task sequences for every model of computer in the school but why do we have two? because of one being x32 bit and one being x64. That's the only difference between the two since they both require enough differences that it was just easier to make two task sequences. Not to mention that our x64 Task Sequence is already massive and loads a little slow in the console :).
Anyway onto the actual subject at hand which is that we had a problem during development that we wanted to select custom software options for the task sequence to install but we did not have a static information source that could be gained from the local computer to perform this. ie we want some computers of the same model to have the Adobe CS4 software installed, but not all, and more over we did not want to have a separate task sequence for every possible combination of custom software to be installed. We then thought about moving on to using a custom screen after the task sequence had started that would allow us to select what software we wanted installed. Sounds simple enough until you actually try to do this since SCCM has now native support for this what so ever which I find amazing that a system so flexible can sometimes be so limiting....
So in our great search of the mighty google I stumbled across this post which kinda got me started on the whole saga
http://technet.microsoft.com/en-us/systemcenter/cm/bb507744

There are some other people that have also done a "similar" thing since we have now implemented(thanks a lot everyone! if you had done this a year earlier it would have made my job a lot easier!)
So off I went and implemented the above post with one exception, that there was no good way to get rid of the Task sequence progress window, or was there?

So lets get started
PreReqs
You will need the following to get this all up and going
-We will need to download the Windows AIK(Automated Installation Kit) from here
and install it onto the computer.

First we need to add HTA support to the WinPE which is not natively in the WinPE which MDT installs

Now since someone else has done all the hard work for me I will just link to there instructions :P
http://t3chn1ck.wordpress.com/2010/01/28/hta-support-in-sccm-boot-images/

Once you have added the HTA support to the WinPE we can now add the scripts and package.
The scripts I have been using are from a collection of sources but the main ideas came from here and here so credit where its due for these two websites :)
The VBS Script I use is as follows
' Hides current Progress UI to bring the HTA to the front
Set ProgressUI = CreateObject("Microsoft.SMS.TsProgressUI")
ProgressUI.CloseProgressDialog
' Create a WshShell object
set sh = CreateObject("Wscript.Shell")
' Call the Run method, and pass your command to it (eg. "mshta.exe MyHTA.hta").
' The last parameter ensures that the VBscript does not proceed / terminate until the mshta process is closed.
call sh.Run("select.hta", 1, True)
 My script name is "select.vbs" when I have used it and the HTA page script is as follows

 <html>
<head>
<title>OSD Front End Script</title>
<HTA:APPLICATION APPLICATIONNAME="OSD GATHER" SCROLL="yes" SINGLEINSTANCE="yes" WINDOWSTATE="normal" BORDER="thin">
</head>

<script language="vbscript" type="text/vbscript">

' Set objects and declare global variables
Set env = CreateObject("Microsoft.SMS.TSEnvironment")
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Sub Window_onLoad
    window.resizeTo 400,550 ' Resize the HTA window on first load
    window.moveTo 100, 10 ' Move the window to the center
End Sub

Sub ButtonFinishClick
    ' ButtonFinishClick is executed by the "Finish" button.
  
    ' Set value of variable to true/false based on whether the checkbox is selected or not
    If FullImage.Checked Then
        strFullImage = "true"
        else strFullImage = "false"
    End If
  
    If Office2010.Checked Then
        strOffice2010 = "true"
        else strOffice2010 = "false"
    End If
    If Office2007.Checked Then
        strOffice2007 = "true"
        else strOffice2007 = "false"
    End If
  
    If AdobeCS4.Checked Then
        strAdobeCS4 = "true"
        else strAdobeCS4 = "false"
    End If
  
  
    ' Set value of variables that will be used by the task sequence, then close the window and allow the task sequence to continue.
    env("OSDFullImage") = strFullImage

    env("OSDOffice2010") = strOffice2010
    env("OSDOffice2007") = strOffice2007

    env("OSDAdobeCS4") = strAdobeCS4
  
    window.Close
End Sub

</script>

<body STYLE="font:14 pt arial; color:white; background-color: #000000">
<span id = "List"></span>
<p>Disk Partition<br>
<input type="checkbox" name="FullImage"> Full Image - C: 100GB<br>
<br>
<br>
<input type="checkbox" name="Office2010"> Office 2010<br>
<input type="checkbox" name="Office2007"> Office 2007<br>
<br>
<input type="checkbox" name="AdobeCS4"> Adobe CS4<br>
<br>
<button accesskey=N type=submit id=buttonFinish onclick=ButtonFinishClick >Install</button>
</body>
</html>
 The HTA Script is called "select.hta". I know I am so creative with my naming!!


As you can see in the script above I have environmental variables being set with the tick boxes which you can customize to your own including adding/removing some of the tick boxes to add more or less options.
piccy of HTA page















Once the enviromental variables are set, they are then accessible by the SCCM Task Sequencer which allows us to make variable dependent Software installs which I will go through how to add a bit later.
both scripts can be downloaded from here


Once you have both the scripts you can dump them into a package to be added to the Task Sequence. As a side note for some reason I have to add Read Access for Guests to my package for it to run correctly but I have not seen anyone else having to do this and this is the only package I have had to do it for. To add a guests access to the package right click on the Access Accounts and Select Generic Access Account.



Click Set, select Guests and OK. Make sure that the Permissions is set to "Read" then select OK

 All this means is the Distribution Point(DP) share can be accessed by anyone, not that there is anything particularly interesting to look at in the scripts but it is something to be aware of because if you have anything with sensitive data that you where planning to include in this bundle, you may want to make another one that doesn't have Guests Access :) I suspect that the problem "may" have to do with the way a VBS script tries to open the HTA page which doesn't really make sense but the important thing is that this fixes it!

The package name I have used is SCCM UI(yeh i know another creative name:P) and now 
Its time to add the Package to the Task Sequence
Add a "Run Command Line" step to the TS and enter the "cscript select.vbs" or what ever your vbs script is called and select the Package that currently contains the scripts








*Edit*

I have had a couple of people have problems with the Task Sequence not continuing when you click finish because the TS is not correctly detecting that the VBS has actually finished and closed. I didn't get this problem, but I have seen it with other software installs. If this happens to you change the following under "Option", "Success codes:"


And Viola! next time you start the TS you will be prompted on which Tick Boxes you would like!







but how do we make the Software installs variable dependent? Its actually surprisingly easy
Select a package or Group you would like the variable dependence to be on and select Options, Add Condition then Task Sequence Variable











 

Type the name of the Variable, set the Condition to equals and the Value as True

This will cause the group/software to only run if that task sequence "condition" is met, which is this case is that OSDOffice2010 is set to true and since all Env variables will return a null or false value unless otherwise set the Variable will only be set during the HTA page.





Since we have our package to download locally from the DP I have then run into the problem of the package generating an error when it tries to run sometimes. This turned out to be that if the Hard Drive did not have a usable NTFS partition, the software would not be able to download and then be run locally. To fix this problem a format and partition setup step must be added before the HTA page that runs to check for a partition and if it does not exist, to create one on the drive.

To do this add a "Format and Partitions Disk" prior to the HTA Page running
Create the partitions that are used in a clean disk scenario since this will only run if it is a clean disk.
and don't forget to select the "Quick format" tick box under the properties of the partition created






  After that this is where we add the condition to only run if there is not an existing partition.
Go to options add add the "If" Statement specifying if "None" of the conditions are true. This will cause the Partitioning to run if the following query returns a false value which would mean that the partition infact does not exist.
 
Select the If statement then add a Query WMI with the following statement 
SELECT * FROM Win32_DiskPartition WHERE Caption LIKE "%Disk #0, Partition #0%"
this will run a WMI Query which will check to see if a partition on Disk 0, and if it does it will cause the Query to return a True value which will then cause the disk partitioning to not run. Simple enough :)

Please note because you are playing with disk partitions, you could potentially wipe disks if you make a mistake so please PLEASE test these before going into production

HAVE FUN!!!

EDIT
I have also been made aware of yet another way of doing the same thing I have done(great, more work wasted, but I will stick with what I have :P). Feel free to have a look
linky: http://osdappchooser.codeplex.com/