Mick's IT Blogs

My blog is here to help solve issues I have encountered and solved, publish scripts I have written, and educate others in understanding areas that are not well covered.

Mick's IT Blogs

Information Technology Zone

12 December 2014

PowerShell: Replicate Permissioning

Here is a script I have written that will replicate the permissions between two folders. Execute the script and you will be prompted for the source and destination folders. It will first parse through and set folder permissions and then parse through file permissions.

You can download the script here.

   Mick Pletcher  
   Copy Permissions  
   This script will replication permissions for both files and folders using  
   robocopy. Change the $SourceDrive and $DestinationDrive variables to match what  
   you need to replicate.  
 $Errors = 0  
 $SourceDrive = Read-Host 'Enter source folder'  
 $DestinationDrive = Read-Host 'Enter destination folder'  
 #Match capitalization with directories  
 $TempDrive = Get-Item -Path $SourceDrive  
 $SourceDrive = $TempDrive.FullName  
 $TempDrive = Get-Item -Path $DestinationDrive  
 $DestinationDrive = $TempDrive.FullName  
 $SourceFolders = Get-ChildItem $SourceDrive -Recurse | ?{ $_.PSIsContainer }  
 $SourceFiles = Get-ChildItem $SourceDrive -Recurse -Force | where { ! $_.PSIsContainer }  
 $DestinationFolders = Get-ChildItem $DestinationDrive -Recurse | ?{ $_.PSIsContainer }  
 $DestinationFiles = Get-ChildItem $DestinationDrive -Recurse -Force | where { ! $_.PSIsContainer }  
 #Copy permissions for folders  
 $Output = robocopy $SourceDrive $DestinationDrive /ZB /E /LEV:0 /COPY:SOU /XF *.* /R:5 /W:5  
 #Verify Folder Permissions Match  
 Write-Host "Folders:"  
 Write-Host "========"  
 For ($Count=0; $Count -le $SourceFolders.Count; $Count++) {  
      If ($SourceFolders[$Count].FullName -ne $null) {  
           $SourceFolder = $SourceFolders[$Count].FullName  
           $DestinationFolder = $DestinationFolders[$Count].FullName  
           Write-Host $SourceFolder"....." -NoNewline  
           $SourceFolderACL = Get-Acl -Path $SourceFolder  
           $DestinationFolderACL = Get-Acl -Path $DestinationFolder  
           For ($Count1=0; $Count1 -le $SourceFolderACL.Access.Count; $Count1++) {  
                If ($SourceFolderACL.Access[$Count1].FileSystemRights -ne $DestinationFolderACL.Access[$Count1].FileSystemRights) {  
           If ($Errors -eq 0) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  
      $Errors = 0  
 $Output = robocopy $SourceDrive $DestinationDrive /ZB /E /LEV:0 /COPY:SOU /XD *.* /R:5 /W:5  
 #Copy permissions for files  
 Write-Host "Files:"  
 Write-Host "======"  
 For ($Count=0; $Count -le $SourceFiles.Count; $Count++) {  
      If ($SourceFiles[$Count].FullName -ne $null) {  
           $SourceFile = $SourceFiles[$Count].FullName  
           $DestinationFile = $DestinationFiles[$Count].FullName  
           Write-Host $SourceFile"....." -NoNewline  
           $SourceFileACL = Get-Acl -Path $SourceFile  
           $DestinationFileACL = Get-Acl -Path $DestinationFile  
           For ($Count1=0; $Count1 -le $SourceFileACL.Access.Count; $Count1++) {  
                If ($SourceFileACL.Access[$Count1].FileSystemRights -ne $DestinationFileACL.Access[$Count1].FileSystemRights) {  
           If ($Errors -eq 0) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  
      $Errors = 0  

11 December 2014

PowerShell: Uninstall All Printers

Recently, we upgraded our print servers and needed to reinstall all of the printers. This script will uninstall all printers. I deployed this script out and had it run as the user and a GPO reinstalled the printer with the new network location.

You can download the script from here: UninstallPrinters.ps1

   Uninstall Printers  
   This script will uninstall all printers for a user  
 .PARAMETER <paramName>  
   <Description of script parameter>  
   powershell.exe -executionpolicy bypass -file UninstallPrinters.ps1  
 $Printers = Get-WmiObject Win32_Printer  
 $EXE = $env:windir + "\system32\printui.exe"  
 $PrintUI = "/dn /n "  
 Foreach ($Printer in $Printers) {  
      If ($Printer.ShareName -ne $null) {  
           Write-Host "Uninstall"$Printer.ShareName"....." -NoNewline  
           $Parameters = $PrintUI + [char]34+ $Printer.Name + [char]34  
           $ErrCode = (Start-Process -FilePath $EXE -ArgumentList $Parameters -Wait -Passthru).ExitCode  
           If ($ErrCode -eq 0) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  

08 December 2014

PowerShell: Deployment Module

This module is designed to make automating the installation of software a breeze. It also provides logging that makes it easy to check and see if there were errors during an installation. The logging has been designed so that there is an installation log file that records all steps in the installation, an application log file that the installer creates, and finally a build.log file that records if the application was successfully installed. The build.log file provides a goto location for checking to see if all applications are installed while generating a golden image. It sequentially numbers easy application that makes it a snap to go and check if all apps are there.

The application log will give a step-by-step logging of the installation as shown below:

In order to properly install the module, it is suggested that you create the following folder: %programfiles%\windowspowershell\modules\Deployment. Next, copy the .PSD1 and PSM1 files to that folder. That is all that is needed to install the module.

The next step to using the module is the use the template I created called install.ps1. The global variables are in an array called $GlobalVariables. The function InitializeVariables is where you go in and make the appropriate modifications to the $Global:LogFile, $Global:Phase, $Global:Sequence, and $Global:Title. The Sequence is populated only if this is an installation that occurs during an image processs. If it is an image process, change Phase to Software Deployment.

Once the InitializationVariables is populated, you will insert the appropriate functions in the field that reads #<Insert Functions to install/uninstall applications>. That is all that is to this. I have been testing this out for a few months and it has made my life as an SCCM administrator much easier. I hope it does the same for you.

The module includes the following functions:

  • Copy-Files
  • Disable-WindowsFeature
  • Enable-WindowsFeature
  • Exit-PowerShell
  • Get-Architecture
  • Get-OSVersion
  • Import-RegistryFile
  • Install-EXE
  • Install-Fonts
  • Install-MSI
  • Install-MSP
  • Install-MSU
  • New-Directory
  • New-FileShortcut
  • New-LogFile
  • New-StartMenuShortcut
  • New-TaskbarShortcut
  • New-URLShortcut
  • Remove-Directory
  • Remove-DirectoryFromUserProfiles
  • Remove-File
  • Remove-FileFromUserProfiles
  • Remove-HKUKey
  • Remove-RegistryKey
  • Remove-StartMenuShortcut
  • Remove-TaskbarShortcut
  • Remove-Variables
  • Set-ConsoleTitle
  • Set-FolderPermissions
  • Set-Variables
  • Start-Task
  • Stop-Task
  • Uninstall-EXE
  • Uninstall-MSI
  • Uninstall-MSIByGUID
  • Uninstall-MSIByName
  • Wait-ProcessEnd
  • Write-LogFile

You can download the module, installer template, and manifest here:
Here is a Youtube tutorial video on how to implement the module:

03 October 2014

PowerShell: Windows Reboot Verification Script

The firm I work for does a weekly reboot. As we revamped our SCCM and AD, it was time to revisit the reboot process. I decided to use PowerShell in conjunction with SCCM to handle this process. To make the process easier to maintain and verify, I split it into two parts. There is the reboot script and the verification script. The reboot script creates an empty log file called NotRebooted.log and reboots the PC. It also deletes any file that is from the previous successful reboot. The verification script looks for the NotRebooted.log file and renames it to Rebooted--02-Oct-2014.log for instance. The filename tells that it was rebooted and the date it happened. This provides for an easy verification without having to search through the event viewer logs. To set this up in SCCM, I created two deployments, Reboot and RebootVerify. I setup RebootVerify to run Reboot first. Of course the Reboot is also set to look for a reboot by the program and not SCCM. That is all that is to doing this. IMO, this makes it much easier to track reboots, at least it has here where I work.

Here are the links to download the scripts:

22 August 2014

PowerShell: Enable Bitlocker on a Dell System

After much research and troubleshooting, here is how to enable bitlocker on a Dell system, including clearing the TPM. The documentation by Dell, Trusted Computing Group, and advice from this thread and this one say that it must be done with physical presence through the BIOS screen. There is a way to do this without having to go through the BIOS. Dell has added three additional settings in the BIOS, tpmppiacpi, tpmppipo, and tpmppidpo. If you enable all three of these settings, then you can clear the TPM ownership without having to physically go into the BIOS. There is a catch. When you clear the TPM, you will be prompted with the screenshot below if you want to accept clearing the TPM when the machine reboots. Once you hit F12, the system will continue. I didn't actually find this to be an issue because the Bitlocker process is next to the last process in the build, so once a technician hits F12, it is only a couple more minutes before the build process is complete. Here is a screenshot of what appears:

In the script that I wrote to clear the TPM, I discovered that it only requires one command to clear it. This documentation from Microsoft's developers center has the list of values for SetPhysicalPresenceRequest and what each value does. Unlike what others were posting, I found that I only needed to use value 5, which is clear TPM.

Now to the actual process. I have created a Powershell script for each step in the process with the script sequentially numbered so you know the process to execute them. Here is the process of enabling TPM, including clearing ownership with a hyperlink to each script in the process:

  1. Enable BIOS Password
  2. Restart
  3. Turn TPM On
  4. Restart
  5. Activate TPM ACPI Support
  6. Restart
  7. Activate PPI Provision
  8. Activate PPI Deprovision
  9. Restart
  10. Clear TPM Ownership
  11. Restart
  12. Activate TPM
  13. Restart
  14. Enable Bitlocker (Manual through Control Panel of MDT/SCCM Task Sequence)

21 August 2014

MDT: Windows Could Not parse or process unattend answer file for pass

This error message was caused, at least in my situation, by the addition of Internet Explorer 11 into the build. It was not integrated, but installed in the golden image as an application. When applying the golden image, the above mentioned error occurred. Luckily, this thread came up with the resolution to comment out <IEWelcomeMsg>false</IEWelcomeMsg> located in the unattend.xml file in the %deployroot%\control\<ImageName> folder. Once I did that, the issue was resolved. You can comment it out by changing the line as follows:



<!-- <IEWelcomeMsg>false</IEWelcomeMsg> -->

18 August 2014

PowerShell: Transferring data between user profiles

Sometimes when you use USMT, it fails for one reason or another. This script is here to transfer user files from one profile to another. It was written so that in the event USMT fails, there is still a means to automate the transfer of user data.

The first step is to login as the end user, or have then login, on the new machine. They can log right back out. This is to create the user profile on the new PC. Once the user has done this, you can now proceed with running the script. It uses robocopy to move the files over. I have also incorporated using PSEXEC to initiate the robocopy command so that the data goes directly from the source to the destination, with no intermediary to slow down the transfer, especially if you are transferring data in a remote office. 

One more feature I have included in the script is the capability of transferring data from an old, renamed profile to a newly created profile on the same machine. 

Before you run this script, you will need to install PSEXEC at some network location for the script to use and also go through and select what you want excluded in the transfer. I have also made the transfer create a log file, which will also need to be customized to your environment. 

You can download the script from here.

   Author: Mick Pletcher  
   Date: 04 March 2014  
   Synopsis: This script will robocopy specific user data from one profile to another on  
                   the same machine, or to a new machine. It will require user input in the following  
                 1) Have the user logout  
                 2) Rename the user profile to <profile>.old  
                 3) Have the user log back in  
                 1) Define Global Memory  
                 2) Get the relative path  
                 3) User input  
                      a) User profile to copy  
                     b) Is the profile to be copied to a new machine  
                     c) If yes, what is the computer name of the new machine  
                 4) Get the OS version of the source machine  
                 5) If copying to a new machine, get the OS version of destination machine  
 #Define Global Variables  
 Set-Variable -Name AdminPassword -Scope Global -Force  
 Set-Variable -Name AdminUsername -Scope Global -Force  
 Set-Variable -Name DestinationComputer -Scope Global -Force  
 Set-Variable -Name DestinationProfile -Scope Global -Force  
 Set-Variable -Name RelativePath -Scope Global -Force  
 Set-Variable -Name SourceComputer -Scope Global -Force  
 Set-Variable -Name SourceProfile -Scope Global -Force  
 Function GetRelativePath {   
      $Global:RelativePath = (split-path $SCRIPT:MyInvocation.MyCommand.Path -parent)+"\"   
 Function GetUserInput {  
      #Define Local Memory  
      Set-Variable -Name Message -Scope Local -Force  
      Set-Variable -Name No -Scope Local -Force  
      Set-Variable -Name Options -Scope Local -Force  
      Set-Variable -Name Result -Scope Local -Force  
      Set-Variable -Name Title -Scope Local -Force  
      Set-Variable -Name Username -Scope Local -Force  
      Set-Variable -Name Yes -Scope Local -Force  
      $Username = Read-Host "Enter the username"  
      $Global:SourceComputer = Read-Host "Enter the computer name of the source system"  
      $Title = ""  
      $message = "Is the profile to be copied to a different machine?"  
      $Yes = New-Object System.Management.Automation.Host.ChoiceDescription "&Yes", "Copy files to profile on different machine"  
      $No = New-Object System.Management.Automation.Host.ChoiceDescription "&No", "Copy files to new profile on same machine"  
      $Options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)  
      $Result = $host.ui.PromptForChoice($title, $message, $options, 0)   
      If ($Result -eq 1) {  
           $Global:DestinationComputer = $Global:SourceComputer  
           $Global:SourceProfile = Read-Host "Enter renamed profile name"  
           $Global:SourceProfile = "\\"+$Global:SourceComputer+"\c$\users\"+$Global:SourceProfile  
           $Global:DestinationProfile = "\\"+$Global:DestinationComputer+"\c$\users\"+$Username  
      } else {  
           $Global:DestinationComputer = Read-Host "Enter the computer name of the new machine"  
           #$Global:SourceProfile = $Env:systemdrive+"\users\"+$Username  
           $Global:SourceProfile = $Username  
           $Global:DestinationProfile = "\\"+$Global:DestinationComputer+"\c$\users\"+$Username  
      $Global:AdminUsername = Read-Host "Enter administrator username"  
      $Global:AdminPassword = Read-Host -AsSecureString "Enter administrator account password"  
      $Global:AdminPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto([System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Global:AdminPassword))  
      $Global:AdminUsername = "[Domain]\"+$Global:AdminUsername  
      #Cleanup Local Memory  
      Remove-Variable -Name Message -Scope Local -Force  
      Remove-Variable -Name No -Scope Local -Force  
      Remove-Variable -Name Options -Scope Local -Force  
      Remove-Variable -Name Result -Scope Local -Force  
      Remove-Variable -Name Title -Scope Local -Force  
      Remove-Variable -Name Username -Scope Local -Force  
      Remove-Variable -Name Yes -Scope Local -Force  
 Function RoboCopyFiles {  
      #Define Local Memory  
      Set-Variable -Name ErrCode -Scope Local -Force  
      Set-Variable -Name ExcludeDir -Scope Local -Force  
      Set-Variable -Name ExcludeFiles -Scope Local -Force  
      Set-Variable -Name EXE -Scope Local -Force  
      Set-Variable -Name Logs -Scope Local -Force  
      Set-Variable -Name Parameters -Scope Local -Force  
      Set-Variable -Name RemoteExec -Scope Local -Force  
      Set-Variable -Name Robocopy -Scope Local -Force  
      Set-Variable -Name Switches -Scope Local -Force  
      $EXE = "\\BNASANIS01\SupportServices\Tools\PSTools\PsExec.exe"  
      $RemoteExec = "\\"+$Global:SourceComputer+[char]32+"-accepteula -u $Global:AdminUsername -p $Global:AdminPassword"+[char]32  
      $Switches = [char]32+"/e /eta /r:1 /w:0"  
      $ExcludeDir = [char]32+"/xd AppData Application* Downloads LocalService *Games* NetworkService *Links* *temp *TEMPOR~1 *cache Local*"  
      $ExcludeFiles = [char]32+"/xf ntuser.* *.exd *.nk2 *.srs extend.dat *cache* *.oab index.* {* *.ost UsrClass.* SharePoint*.pst history* *tmp*"  
      $Logs = [char]32+"/log:"+$Env:windir+"\waller\Logs\ApplicationLogs\ProfileCopy.log"  
      $Parameters = $Switches+$ExcludeDir+$ExcludeFiles+$Logs  
      $Arguments = $RemoteExec+$Env:windir+"\system32\robocopy.exe"+[char]32+$Env:systemdrive+"\users\"+$Global:SourceProfile+[char]32+$Global:DestinationProfile+$Parameters  
      $ErrCode = (Start-Process -FilePath $EXE -ArgumentList $Arguments -Wait -Passthru).ExitCode  
      #Cleanup Local Memory  
      Remove-Variable -Name ErrCode -Scope Local -Force  
      Remove-Variable -Name ExcludeDir -Scope Local -Force  
      Remove-Variable -Name ExcludeFiles -Scope Local -Force  
      Remove-Variable -Name EXE -Scope Local -Force  
      Remove-Variable -Name Logs -Scope Local -Force  
      Remove-Variable -Name Parameters -Scope Local -Force  
      Remove-Variable -Name RemoteExec -Scope Local -Force  
      Remove-Variable -Name Robocopy -Scope Local -Force  
      Remove-Variable -Name Switches -Scope Local -Force  
 Function CopyFiles ($FileSource,$FileDest,$FileFilter) {  
      $Dest = $FileDest  
      $Files = Get-ChildItem $FileSource -Filter $FileFilter  
      If ($Files.Count -eq $null) {  
           Write-Host "Copy "$Files.Name"....." -NoNewline  
           Copy-Item $Files.FullName -Destination $Dest -Force  
           $Test = $Dest + "\"+$Files.Name  
           If (Test-Path $Test) {  
                Write-Host "Success" -ForegroundColor Yellow  
           } else {  
                Write-Host "Failed" -ForegroundColor Red  
      } else {  
           For ($i = 0; $i -lt $Files.Count; $i++) {  
                $File = $Files[$i].FullName  
                Write-Host "Copy"$Files[$i].Name"....." -NoNewline  
                Copy-Item $File -Destination $Dest -Force  
                $Test = $Dest + "\"+$Files[$i].Name  
                If (Test-Path $Test) {  
                     Write-Host "Success" -ForegroundColor Yellow  
                } else {  
                     Write-Host "Failed" -ForegroundColor Red  
 #Copy Outlook Signatures  
 $TempSource = "\\"+$SourceComputer+"\c$\users\"+$SourceProfile+"\AppData\Roaming\Microsoft\Signatures"  
 $TempDestination = "\\"+$DestinationComputer+"\c$\users\"+$DestinationProfile+"\AppData\Roaming\Microsoft\Signatures"  
 CopyFiles $TempSource $TempDestination "*.*"