PoshCode Archive  Artifact [45ed2cf000]

Artifact 45ed2cf000874077a86827a4ba1c63b02ae3ca100e35d9915525f15816ba4cf1:

  • File RDC-RemoteApp-PassMan.ps1 — part of check-in [09aa656f91] at 2018-06-10 13:02:43 on branch trunk — See my blog post about this http://www.networkworld.com/community/blog/rdc-remoteapp-how-does-user-change-their-pass for the original info. For those that do not want to click through: (user: tysonkopczynski size: 12514)

# encoding: ascii
# api: powershell
# title: RDC RemoteApp PassMan
# description: See my blog post about this http://www.networkworld.com/community/blog/rdc-remoteapp-how-does-user-change-their-pass for the original info.  For those that do not want to click through:
# version: 0.1
# type: function
# author: tysonkopczynski
# license: CC0
# function: Initialize-ENV
# x-poshcode-id: 1910
# x-archived: 2017-05-16T03:33:06
# x-published: 2011-06-09T14:05:00
#
# I recently ran into a very interesting scenario with RDC RemoteApp. Basically, we had a client that was using RDC RemoteApp to deploy a medical related application. For their deployment scenario they wanted to create and distribute RDP files to remote users who were not on the organizations internal network. After semi-going live with their deployment they turned to us and asked, What about password changes?
# To be honest, I never gave password changes much thought with RemoteApp. After all, with most deployments the user has a desktop that is a member of the domain or they are coming through Web Access and we can front the password changes with something like UAG. However, with just RemoteApp via an RDP file on a non-domain member machine there really isnt a way for users to change their password. Yes, you heard me correctly
 there isnt a way for users to change their password or get notified about impending password expiration.
# To understand why this is the case you have to take two things into consideration about RemoteApp. First, the primary feature of RemoteApp is that it provides seamless windows. In other words, the application looks like it is running locally on the users machine. Secondly, to achieve its seamless windows magic, RemoteApp does not use Windows Explorer as the users shell on the RDS Session Host server. Instead, it uses RDPSHELL.EXE which loads a set of Windows event hooks into the users session that allow it to monitor and manage the state of all windows on the desktop. As a result, the following things are true about a RemoteApp session:
# A user doesnt see the desktop of the RemoteApp session.
# A user doesnt see password notifications.
# Login scripts are not processed unless specified using a GPO.
# Dialog boxes from a logon script and sometimes from the published application itself are not shown.
# So
 how does one work around the features of RemoteApp to allow users to change their passwords? Well the solution that I came up with involves PowerShell. While I cant necessarily publish the source code, I can describe what I did.
# Overall, I needed to provide users with a GUI to change their passwords. However, to work around RemoteApp, I had to basically write a PowerShell based GUI that was then published as the intended application. Then depending on the outcome of this GUI the actual intended application was started and the password change GUI was closed. To create the password change solution the following steps were used:
# First, download the PowerShellPack: http://code.msdn.microsoft.com/PowerShellPack.
# Next, grab the WPK module from the PowerShellPack and copy it into the folder that will house the future password change script.
# Next, write a PowerShell script that does the following:
# Imports the WPK module.
# Determine when the logged on users password is going to expire (password policy settings can either be hard coded or determined from Active Directory).
# If the users password isnt going to expire in a specified minimum period (say 10 days). Then intended application is just started.
# If the users password is going to expire in 10 days a password change GUI is displayed using the WPF cmdlets from the WPK module. With my GUI there were three PasswordBoxs (current password, new password, and confirm new password), two buttons (change and cancel), and a TextBlock for displaying messages.
# For times when the users password will expire within 10 days and greater then one day the GUI allows the user to cancel and launch the application.
# For times when the users password will expire in some maximum period (say less than one day) the cancel button is disabled.
# When a user has filled in the correct password information (old and new) they can click Change. Upon clicking Change, the Password method of the DirectoryEntry class is used to change the users password. Once the password has been changed, the password change GUI is closed and the intended application is started.
# Next, a batch file needs to be created that executes the PowerShell script using the following command: powershell.exe -STA -NoProfile -WindowStyle Hidden -Command “C:\PassMan\PassMan.ps1”. Notice the usage of the Hidden WindowStyle. This ensures that the PowerShell console is not shown to the user when the script is executed.
# Finally, copy the password change GUI to all of the RDC Session Host servers and publish the batch file as a RemoteApp.
# Hopefully this helps someone

#
##################################################
# ENV Setup
##################################################
#-------------------------------------------------
# Initialize-ENV
#-------------------------------------------------
# Usage:	Used to build the execution ENV.
# **Function is not for interactive execution.**
#-------------------------------------------------
function Initialize-ENV {
	Import-Module .\WPK
	
	# Modify these variables to meet your needs.  The MinPasswordAge
	# and MaxPasswordAge variables can be pulled from AD.  But, for this 
	# version they are hardcoded. The ApplicationPath is the application
	# that you want to start after the PassMan screen.
	$Global:MinPasswordAge = 30 #Number of days to start warning.
	$Global:MaxPasswordAge = 40 #Number of days to force password change.
	$Global:ApplicationPath = "C:\Program Files\Windows NT\Accessories\wordpad.exe"
	}

##################################################
# Functions
##################################################
# Note: These functions are used to complete the various 
# 		automation tasks for this script.
#-------------------------------------------------
# Get-CurrentIdentity
#-------------------------------------------------
# Usage:	Used to get the identiy for the currently
#			logged on user.
#-------------------------------------------------
function Get-CurrentIdentity {
	$CurrentUser = [System.Security.Principal.WindowsIdentity]::GetCurrent() 
	New-Object System.Security.Principal.WindowsPrincipal($CurrentUser)
	}
	
function Get-ADUser {
	param ([string]$UserName)
	$ADDomain = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() 
	$Root = [ADSI] "LDAP://$($ADDomain.Name)"
	$Searcher = New-Object System.DirectoryServices.DirectorySearcher $Root
	$Searcher.Filter = "(&(objectCategory=person)(objectClass=user)(sAMAccountName=$UserName))"
	$Searcher.FindOne()
	}

##################################################
# Main
##################################################
try {
	Initialize-ENV

	$Global:UserIdentity = Get-CurrentIdentity
	$Global:UsersAMAccountName = (($UserIdentity.Identity.Name).Split("\"))[1]
	$Global:UserObject = Get-ADUser $UsersAMAccountName
	$Global:PwdLastSet = [datetime]::FromFileTime($UserObject.properties.pwdlastset[0])
	$Global:PwdLastSetDays = (([System.DateTime]::Now) - $PwdLastSet).TotalDays
	$Global:PwdExpire = "{0:N0}" -f ($MaxPasswordAge - $PwdLastSetDays)
	$Global:ExistingPassword
	$Global:NewPassword1
	$Global:NewPassword2

	if ($PwdLastSetDays -ge $MinPasswordAge){
		New-Window -Title "Password change for $($UsersAMAccountName)" `
			-WindowStartupLocation CenterScreen `
			-Width 320 -Height 255 -Show `
			-ResizeMode NoResize {
			$BColor = New-SolidColorBrush -Color "#E0E0E0"
			New-Grid -Rows 'Auto', 'Auto', 'Auto', 'Auto', 'Auto', 'Auto' `
				-Columns 'Auto', '150' -Background $BColor {
				New-Label -Name rtbHeader `
					-VerticalContentAlignment Top `
					-HorizontalContentAlignment Left `
					-Row 0 -Column 0 -ColumnSpan 2 `
					-Margin "10 10 0 0" -Width 320 -Height 30 `
					-Foreground Red -FontWeight Bold
				New-TextBlock -Name rtbMessage `
					-HorizontalAlignment Left `
					-VerticalContentAlignment Top `
					-HorizontalContentAlignment Left `
					-Row 1 -Column 0 -ColumnSpan 2 `
					-Margin "10 0 0 0" -Width 275 -Height 80 `
					-Foreground Red -FontWeight Bold -TextWrapping "0"
				New-Label -Content "Existing password:" `
					-Row 2 -Column 0 -Margin "10 0 0 0"
				New-PasswordBox -Name pbExistingPassword `
					-Row 2 -Column 1 -Width 130 `
					-VerticalContentAlignment Center `
					-HorizontalAlignment Left -PasswordChar "*" `
					-On_PasswordChanged {$pbNewPassword1.IsEnabled = $True; $pbNewPassword2.IsEnabled = $True; $btnChange.IsEnabled =$True; $ExistingPassword = $this.password}
				New-Label -Content "New password:" `
					-Row 3 -Column 0 -Margin "10 0 0 0"
				New-PasswordBox -Name pbNewPassword1 `
					-Row 3 -Column 1 -Width 130 `
					-VerticalContentAlignment Center `
					-HorizontalAlignment Left -PasswordChar "*" `
					-On_PasswordChanged {$NewPassword1 = $this.password}
				New-Label -Content "Retype New password:" `
					-Row 4 -Column 0 -Margin "10 0 0 0"
				New-PasswordBox -Name pbNewPassword2 `
					-Row 4 -Column 1 -Width 130 `
					-VerticalContentAlignment Center `
					-HorizontalAlignment Left -PasswordChar "*" `
					-On_PasswordChanged {$NewPassword2 = $this.password}
				New-Button -Name btnChange -Content "_Change" `
					-HorizontalAlignment Left `
					-Row 5 -Column 2 `
					-Margin "0 10 0 0" -Width 50
				New-Button -Name btnCancel -Content "_Cancel" `
					-On_Click {$Window.Close(); Start-Process $ApplicationPath} `
					-HorizontalAlignment Left `
					-Row 5 -Column 2 `
					-Margin "55 10 0 0" -Width 50
				}
			} -On_Loaded {
				if (($PwdLastSetDays -ge $MinPasswordAge) -or ($PwdLastSetDays -ge $MaxPasswordAge)) {
					$rtbHeader = $Window | Get-ChildControl rtbHeader
					$rtbMessage = $Window | Get-ChildControl rtbMessage
					$pbNewPassword1 = $Window | Get-ChildControl pbNewPassword1
					$pbNewPassword2 = $Window | Get-ChildControl pbNewPassword2
					$btnChange = $Window | Get-ChildControl btnChange
					$btnCancel = $Window | Get-ChildControl btnCancel
					
					# Disable relevate controls
					$pbNewPassword1.IsEnabled = $false
					$pbNewPassword2.IsEnabled = $false
					$btnChange.IsEnabled = $false
				
					# If we haven't gotten to the MaxPasswordAge The user can change their password.
					if ($PwdLastSetDays -ge $MinPasswordAge) {
						$rtbHeader.Content = "***WARNING***"
						$rtbMessage.Text = "Your password will expire in $($PwdExpire) days. Please change your password now or click Cancel to continue."
						}
					
					# If we are at the MaxPasswordAge. The user must change their password.
					if ($PwdLastSetDays -ge $MaxPasswordAge) {
						$rtbHeader.Content = "***WARNING***"
						$rtbMessage.Text = "Your password is about to expire. You must change your password now."
						$btnCancel.IsEnabled = $false
						}
					
					# Here is the password change logic.
					$Command = {if (!($NewPassword1 -eq $NewPassword2)){
									$rtbHeader.Foreground = "Red"
									$rtbHeader.Content = "***WARNING***"
									$rtbMessage.Foreground = "Red"
									$rtbMessage.Text = "Passwords do not match, please try again."	
									}
								else {
									$rtbHeader.Foreground = "Black"
									$rtbHeader.Content = "Progress..."
									$rtbMessage.Foreground = "Black"
									$rtbMessage.Text = "Please wait, trying to change password."
									
									try{
										$User = $UserObject.GetDirectoryEntry()
										$User.psbase.invoke("ChangePassword",$ExistingPassword, $NewPassword1)
										
										# Nothing to do, start application
										Start-Process $ApplicationPath
										}
									catch {
										$rtbHeader.Foreground = "Red"
										$rtbHeader.Content = "***ERROR***"
										$rtbMessage.Foreground = "Red"
										$rtbMessage.Text = $_.Exception.InnerException.Message
											}
									}
								}
					$btnChange.Add_Click($Command)
					}
				}
			}
		else{
			# Nothing to do, start application
			Start-Process $ApplicationPath
			}

	}
catch {
	# Nothing to do, start application
	Start-Process $ApplicationPath
	}