Looping in Labtech (Connectwise Automate)

I’ve been looking for this article on several occasions, and there it was, in LinkedIn! Re-posting here….

So, you want to do a loop in Labtech.  No problem – as long as you keep a few things in mind.

First, you have to follow the logic!  Use script notes to let yourself know what you want to accomplish.  Also, you need to use Labels and Script GoTo to create the loop. Labels are Script Notes with “:” preceding the label.  Short, simple labels are best.  Know where you want to exit the loop and when to continue the loop.

In the above image, we are taking a “String of Stuff” and splitting it, then parsing the string for each start and taking two characters after that to be a Percent Free.

Note the “SET: @Counter@ = @Counter@ – 1” entry.  This is a Script Math line.  It is, however, not written as shown.  If you put @Counter@ in the variable section, it will use the Value of the variable previously set, so you may wind up with @4@ set to ‘3’ if you try this. 

Despite what the description says, it does matter whether you use @ or not!

In order to do a Loop successfully, you need to make sure that the “Variable” section of each Variable Set, Script Math, etc. that may change value in the loop (or otherwise) does not have the ‘@’ with the variable name.

Users can’t see on RDP Session

Sometimes we have a larger resolution on an RDP server and users complain they can’t see – or, just a couple users complain and everyone else thinks it is fine. Here are some methods to Magnify an RDP session, Make Text Bigger on the RDP session or, increase the scaling directly from the Remote Desktop Session’s .RDP file.

Using Magnifier on RDP session

First, lets change the settings on Magnifier, because it starts at 100% increments and that can just look ugly.  Start button and type “mag” should bring up the following.

There is a drop-down to pick the zoom level increments – pick 10% to start with

The directions on this page tell you how to use it.  (Ignore make everything bigger)

You can now turn on the Magnifier by pressing the Windows logo key on the keyboard, then the Plus Sign.

To turn the magnifier off, hold down the Windows Logo key and press ESC key.

Adjust Font Size on Remote Desktop session:

Click start or the Search in the lower left corner of the screen and type “Make Text Size Bigger” (may not have to type the whole thing)

Click on the Make text size bigger (system settings)

Drag the slider bar to the size you need, then click Apply

Adjusting the Text of the .RDP file

This is the last effort if neither of the above work properly, because it involves editing the .RDP using Notepad++ – or Notepad, if you don’t like better programs.

You will need the .RDP file you use to connect – or you can Save As on the Remote Desktop and save that to the local desktop.

You will need to right click and Edit with Notepad++, or select Open With – then choose Notepad

You may need to choose another app, then More Apps and scroll down to find Notepad, then click OK.

When open in Notepad++ (or Notepad), you will see a lot of text.  Scroll down to the bottom.

Add the following at the bottom:
desktopscalefactor:i:125
devicescalefactor:i:125

You can adjust these numbers, but I think 125% is good to start with.

While this is open, we can also make some performance improvements! Let’s find :

redirectsmartcards:i:1 and change the 1 to a 0 (zero). So it would be:

redirectsmartcards:i:0

And you may want to change the session bpp:i: – I suggest:
session bpp:i:24

Now, save and close Notepad++ (or Notepad).

Use the .RDP file to connect to your session.

Stop and Prevent Office apps from Saving to One Drive by Default

Are you troubled by the way Office M365 apps – Word, Excel, PowerPoint, Outlook – all save to One Drive by default, because that can get pretty annoying.

Fortunately, preventing this behavior is rather simple – and not as extreme as unlinking and uninstalling One Drive – which you may be using for other reasons (or not).

Open Word, then click on Options in the lower left corner

Click on Save in the left menu, then check the Save to Computer by default.

This sets the option for the whole office suite of products, though, I would double check the next time you use them.

Edit Settings for RDP file in RDWeb

RDP has a lot of issues, one is how to get the users to use the right settings for the .RDP file they use to connect. In the past, I’ve downloaded the .rdp from RDWeb, edited it and distributed the file to end-users manually – via email or group policy to put it on their desktop, etc. That was getting old, so now, I have found a way to manage it via the Registry on the RDP connection broker.

One thing I’d like to mention before getting into the code is another registry entry : Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\CentralPublishedResources\PublishedFarms\{TheNameOfYourCollection}\RemoteDesktops\{YourRemoteDesktop} and the entry ShowInPortal – set that to 1 to show, 0 to hide (which 0 is the default).

Now, on to editing the RDPFileContents – oh, you saw that in the image above? Same location in the registry. This entry has the whole RDP file contents in it – new lines and all – that is downloaded from RDWeb links. Sure, you could (carefully) edit the line in the registry – but if you accidentally take out a new line between entries, you can’t put it back in! So, the better way to do it is to download the file from RDWeb, Edit with Notepad++ (or similar, I guess plain ole notepad would work as well).

You can now edit the file – I suggest Session bpp:i:24, redirectsmartcards:i:0 (This can clear up performance issues!), and drivestoredirect:s:C:\; (note the ; after) at a minimum. See the whole list of settings at Microsoft. ( Note the Remote Desktop Services column )

Once you have the settings down – and have tested using the file you just edited – you can use the following Powershell (edit the variables, of course…) Oh, you do need to run this on your RD Web Server, if it is separate from your Gateway, Connection Broker, Host, etc., to set the .rdp file configuration in your RDWeb registry. For RemoteApps, change the RemoteDesktops to RemoteApps in the registry path.

##Powershell to create a multi-line registry entry from a file
#_RDP registry location is : Computer\HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\CentralPublishedResources\PublishedFarms
##_Gather the Variable Information
#_The file to import the information from
$FileToImport = "\\Path\to\your\saved\RDP-Desktop.rdp"
#_Set the entry type - for RDPFileContents, this is String.
$RegType = "string"  # For New-ItemProperty, but not used in Set-ItemProperty
#_Set the path to the Key (Folder) level of registry.
$RegPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Terminal Server\Test\PublishedFarms\{YourRDPName}\RemoteDesktops\{YourRDPName}"
#_The Registry entry to Edit
$RegEntry = "RDPFileContents"
#_Import the file contents to a variable
$rContents = [system.string]::Join("`r`n",(Get-Content -Path $FileToImport))
# Assign the value
Set-ItemProperty -Path $RegPath -Name $RegEntry -Value $rContents -Force

Now your settings will be downloaded from RDWeb! No more distributing .rdp files with customized settings! With that set up and all user workstations set to not use UDP for RDP sessions, things should run smoothly!

Log off Idle User with Quser and Powershell

I recently had a reason to need a solution to log off idle users from a workstation. “Group Policy!” was my first thought – but, no, the group policy idle logoff is only for Remote Desktop Sessions. Looking online, there were a lot of posts pointing this out – and decrying why Microsoft is so stupid as to not have this available. Its 2022 for crying out loud! I pieced together a script to log off idle users that actually works in a real company environment.

In my searching, however, I did find quser and powershell. You use quser, parse the output and log off idle users over an hour. Easy peasey, right? Nope. Not at all. For one thing, quser has some reality issues. ( I posted asking for help on sorting it out! )

The first thing about the quser output is how to properly put it in a good, powershell readable table. Some have suggested “convertfrom-string” – but that doesn’t handle the “Idle Time” and “Logon Time” very well, due to spaces.

The ‘original’ used :
Select-Object -Skip 1 | ForEach-Object {($_ -replace '\s{2,}', ',').Trim()} | ConvertFrom-Csv -Header $Header |

But that sometimes had issues as well, because if another user is logged on (switch user), the Session is empty, so you have a missing column and everything after ‘session’ gets shifted left by one.

Olaf (on the reality issues link above) helped me see how to use substring() to get what will work most, if not all, of the time. I haven’t seen an issue for it. I guess I may have been just too lazy to count it out. Either, way, it works.

The next thing is that “Idle Time” outputs a lot of different things – it can be ‘none’ or a ‘.’ or a measure of time ‘5’ minutes, ’14:54′ hours and minutes, or ‘1+13:33’ days plus hours and minutes. So, you have to, in a real-world situation, make allowances for such output and do your best to If/Then for all iterations.

The next thing is, that in Windows 10 and below, “Idle Time” doesn’t seem to actually be the individual user’s Idle Time. In testing, it seems more like ‘the time since any user last had a log on event.’
UserName SessionName ID State IdleTime
-------- ----------- -- ----- --------
bkearan console 1 Active 1+03:57
bktest 2 Disc 1+03:57

See how both users have the same ‘Idle Time?’ Even the Active user has an idle time. Now, in Windows 11, it appears to be closer to the user’s idle time (at least on my home, pc – my company laptop is 11, but has the same issue as 10) – but corporate environments haven’t all got to Windows 11. Some places still have Windows 7, but I digress. In Windows 10, you can’t use only idle time to log off a user – as it will log off an active user. … Yes, I got logged off as I was testing this theory. I clicked run in PowerShell ISE and got logged off immediately.

Now for the final kicker – a user is “Active” as long as they are the primary account logged on. Yep, the last logged on user NEVER goes to an Idle state. So far, I haven’t found a way around this last issue. However, if another user logs on after an hour – the Idle Time counter resets and the first user won’t be logged off for another hour.

I have the script set to run as a scheduled task on boot of the computers that need to log off users (so as not to get bogged down with multiple user sessions).

And I just added the Days as I was writing this. Just so much to parse! My Windows 11 laptop has been running the script for 3 days, 4 hours and 41 minutes – and I haven’t been near the laptop for two days, but my logged on user is still showing “Active.”

Then, I started looking for how to get the actual Idle time of the whole system. Now, I have added, as I write this, code to detect the time the system has not received any input whatsoever. So, here is the code that will log off ‘disconnected’ users and even the Console user if the whole system has been idle for over an hour: — and of course that doesn’t work. The Idle Console piece doesn’t get any input when tracking as the SYSTEM account – so logs off everything after an hour – even new logons. So I had to break it up into TWO scripts. The first script (below) is the one to run as a scheduled task on startup, run as SYSTEM.

#
# Complied from pieces of code and additions by Bobby Kearan with inspiration from
# https://www.reddit.com/r/PowerShell/comments/8r56tr/getting_idle_time_for_logged_on_domain_users/
# and Thanks to Olaf (https://forums.powershell.org/u/Olaf) for helping with parsing the output of quser properly


function Log-Message {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message
    )

    $currentSize = 0
    if (Test-Path $Logfile) {
        $currentSize = (Get-Item $Logfile).length/1MB
    }

    if ($currentSize -gt 10) {
        if (Test-Path $oldLogFile) {
            Remove-Item $oldLogFile -Force
        }
        Move-Item $Logfile $oldLogFile -Force
    }

    Add-Content -Path $Logfile -Value ("[" + (Get-Date) + "] " + $Message)
}

function Get-QUserInfo {
    [CmdletBinding()]
    param (
        [Parameter()]
        [string[]]
        $ComputerName = $env:COMPUTERNAME
    )

    begin
        {
        $Header = 'UserName','SessionName','ID','State','IdleTime','LogonTime'
        $No_Connection = '-- No Connection --'
        }

    process
        {
        foreach ($CN_Item in $ComputerName)
            {
            if (Test-Connection -ComputerName $CN_Item -Count 1 -Quiet)
                {
                $QuserOutput = quser /server:$CN_Item
                $QuserOutput -split "`n" | Select-Object -Skip 3
                $QuserOutput | Select-Object -Skip 1 |
                    ForEach-Object {
                    
						$IdleTime = $_.Substring(54, 11).trim()
						#Write-Host $IdleTime
						If (($IdleTime -eq 'none') -or ($IdleTime -eq '.') -or ($IdleTime -eq '$Null')) {
						$IdleTime = "Not Idle"
						}
						#Write-Host "Testing Idle Time " $IdleTime "for the + sign"
						IF ($IdleTime -like '*+*') {
                        $IdleTime = $IdleTime.replace("+",":")
                        #Write-Host $IdleTime " was the Plus sign replaced + ? "
						}
              
					If ($IdleTime -as [DateTime]) {
                        If ($IdleTime -match "\d\d\:\d\d"){
							#$IdleTime = [timespan]$IdleTime
							$Idleness = $IdleTime -split ":"
                        #Write-Host $Idleness.length
							If($Idleness.length -eq 2){
							$IdleTime = New-Object -TypeName PSObject
							$d = [ordered]@{Days=0;Hours=$Idleness[0];Minutes=$Idleness[1]}
							$IdleTime | Add-Member -NotePropertyMembers $d -TypeName Asset
							}
                        Else {
							$IdleTime = New-Object -TypeName PSObject
							$d = [ordered]@{Days=$Idleness[0];Hours=$Idleness[1];Minutes=$Idleness[2]}
							$IdleTime | Add-Member -NotePropertyMembers $d -TypeName Asset
							}
                        #Write-Host $Idleness[0] " = " $IdleTime.Hours
                        }
                        ELSE {
                          $IdleTime = New-TimeSpan -Start $IdleTime -End (Get-Date)
                          #Write-Host "IdleTime was a datetime-" $IdleTime
                          }
                       }
					   #Write-Host $IdleTime
						$final = $_.length - 65
						$Username = $_.Substring(1, 22).trim()
                    
						[PSCustomObject]@{
						UserName    = $_.Substring(1, 22).trim()
						SessionName = $_.Substring(23, 19).trim()
						ID          = $_.Substring(42, 3).trim()
						State       = $_.Substring(46, 8).trim()
						IdleTime    = $IdleTime
						LogonTime   = $_.Substring(65, $final).trim()
                        }
					}
                }
                else
                {
                [PSCustomObject]@{
                    ComputerName = $CN_Item
                    UserName = $No_Connection
                    SessionName = $No_Connection
                    ID = $No_Connection
                    State = $No_Connection
                    IdleTime = $No_Connection
                    LogonTime = $No_Connection
                    }                
                }
            } # end >> foreach ($CN_Item in $ComputerName)
        } # end >> process {}

    end {}

    } # end >> function Get-QUserInfo

    #$Info = Get-QUserInfo -ComputerName localhost

    $Logfile = "c:\scripts\LogOff.log"
    $oldLogFile = "c:\scripts\Logoff.old.log"
    #Initalize Log file
    Log-Message "Log Start: " + (Get-Date)
    

    $state = "Active"
    while ($state -eq "Active") {
    $Info = Get-QUserInfo -ComputerName LocalHost
    foreach ($user in $Info) {
        if ($user.State.Trim() -ne "Active" -and $user.IdleTime.Hours -ge 1) {
            logoff $user.ID
            Log-Message "$($user.State) User $($user.UserName) has been idle $($user.IdleTime.Days) Days $($user.IdleTime.Hours) hours, $($user.IdleTime.Minutes) Minutes - Logged On Time = $($user.IdleTime)"
        }
    }
    Start-Sleep -Seconds 260
    }

I did add a couple things after posting. That would be adding in self-cleaning Logging. Now, the second one, runs as a scheduled task as well, but on logon of any user, and it runs in the console session.

# Written by Bobby Kearan with inspiration and code from: 
# https://www.reddit.com/r/PowerShell/comments/8r56tr/getting_idle_time_for_logged_on_domain_users/
# and, finally, https://stackoverflow.com/questions/15845508/get-idle-time-of-machine
#
Add-Type @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace PInvoke.Win32 {

    public static class UserInput {

        [DllImport("user32.dll", SetLastError=false)]
        private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

        [StructLayout(LayoutKind.Sequential)]
        private struct LASTINPUTINFO {
            public uint cbSize;
            public int dwTime;
        }

        public static DateTime LastInput {
            get {
                DateTime bootTime = DateTime.UtcNow.AddMilliseconds(-Environment.TickCount);
                DateTime lastInput = bootTime.AddMilliseconds(LastInputTicks);
                return lastInput;
            }
        }

        public static TimeSpan IdleTime {
            get {
                return DateTime.UtcNow.Subtract(LastInput);
            }
        }

        public static int LastInputTicks {
            get {
                LASTINPUTINFO lii = new LASTINPUTINFO();
                lii.cbSize = (uint)Marshal.SizeOf(typeof(LASTINPUTINFO));
                GetLastInputInfo(ref lii);
                return lii.dwTime;
            }
        }
    }
}
'@

function Log-Message {
    param (
        [Parameter(Mandatory = $true)]
        [string]$Message
    )

    $currentSize = 0
    if (Test-Path $Logfile) {
        $currentSize = (Get-Item $Logfile).length / 1MB
    }

    if ($currentSize -gt 5) {
        if (Test-Path $oldLogFile) {
            Remove-Item $oldLogFile -Force
        }
        Move-Item $Logfile $oldLogFile -Force
    }

    Add-Content -Path $Logfile -Value ("[" + (Get-Date) + "] " + $Message)
}

$Logfile = "c:\scripts\LogOff-console.log"
$oldLogFile = "c:\scripts\Logoff-console.old.log"

Log-Message "Log Start"

$state = "Active"

while ($state -eq "Active") {
    $idleTime = [PInvoke.Win32.UserInput]::IdleTime

    if ($idleTime.Hours -ge 1) {
        $sessionid = (Get-Process -PID $pid).SessionID
        Log-Message "$env:USERNAME has been idle for $($idleTime.Hours) hours and $($idleTime.Minutes) minutes."
        logoff $sessionid
    }

    Start-Sleep -Seconds 260
}

To assist with this, I wrote this powershell to create the scheduled tasks (mostly) – it launches task scheduler so you can check and verify.

# Check if the files are in C:\scripts
$scriptPath = "C:\scripts"
$files = @("Logoffonidle.ps1", "LogoffOnIdle_Console.ps1")

foreach ($file in $files) {
    if (-not (Test-Path "$scriptPath\$file")) {
        Write-Output "Error: $file is not present in $scriptPath"
        exit
    }
}


# Create or Update scheduled tasks
$TaskSettings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -StartWhenAvailable -WakeToRun
$TaskSettings.ExecutionTimeLimit = 'PT0S'
$TaskSettings.Compatibility = 'win8'

$actionStartup = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File $scriptPath\Logoffonidle.ps1"
$triggerStartup = New-ScheduledTaskTrigger -AtStartup

$taskNameStartup = "LogOffOnIdleAtStartup"
if (Get-ScheduledTask -TaskName $taskNameStartup -ErrorAction SilentlyContinue) {
    Set-ScheduledTask -TaskName $taskNameStartup -Action $actionStartup -Trigger $triggerStartup -Settings $TaskSettings
} else {
    Register-ScheduledTask -Action $actionStartup -Trigger $triggerStartup -TaskName $taskNameStartup -User "SYSTEM" -RunLevel Highest -Settings $TaskSettings
}

$actionIdle = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-WindowStyle Hidden -ExecutionPolicy Bypass -File $scriptPath\LogoffOnIdle_Console.ps1"
$triggerIdle = New-ScheduledTaskTrigger -AtLogOn

$taskNameIdle = "LogOffOnIdleWhenUserIdle"
$principalIdle = New-ScheduledTaskPrincipal -UserId "NT AUTHORITY\INTERACTIVE" -RunLevel Limited
if (Get-ScheduledTask -TaskName $taskNameIdle -ErrorAction SilentlyContinue) {
    Set-ScheduledTask -TaskName $taskNameIdle -Action $actionIdle -Trigger $triggerIdle -Principal $principalIdle -Settings $TaskSettings
} else {
    # Register for any user that logs on
    Register-ScheduledTask -Action $actionIdle -Trigger $triggerIdle -TaskName $taskNameIdle -Principal $principalIdle -Settings $TaskSettings
}

taskschd.msc

This should be working for both disconnected and console sessions now.

Run the script and make sure it creates both Scheduled Tasks.

Open the LogOffOnIdleAtStartup scheduled task and make sure it is set the Configure For: to the latest system there is available

Now open the LogOffOnIdleWhenUserIdle scheduled task and make sure it is set for: to the latest system there is – AND  the User is set to Interactive (or “NT AUTHORITY\INTERACTIVE”) and Run Only when user is logged on.

Edit each task to Uncheck the Stop the task if it runs more than 3 days.  We don’t want these tasks to stop.

Run task as soon as possible after scheduled start is missed.  Make the other show changes as needed:

The script creates a log of every logoff event it does in c:\scripts\logoff.log

If you need to keep a system alive when important tasks are running, add to the top section of LogoffIdle:

# Important processes that should prevent logging off
$KeepAliveTasks = @("robocopy", "ping", "Syncing")

Then, adjust the While loop:

while ($state -eq "Active") {
    $idleTime = [PInvoke.Win32.UserInput]::IdleTime

    $importantProcessRunning = Get-Process | Where-Object {
        $_.Name -in $KeepAliveTasks -or 
        $KeepAliveTasks -contains $_.MainWindowTitle
    }

    if ($idleTime.Hours -ge 1 -and (-not $importantProcessRunning)) {
        Log-Message "User $muser has been idle for $($idleTime.Hours) hours and $($idleTime.Minutes) minutes. Attempting logoff."
        
        # Trying a direct PowerShell approach for logoff
        $sessionid = (Get-Process -PID $pid).SessionID
        logoff $sessionid
    }

    Start-Sleep -Seconds 260
}

DFSRMig stuck at Eliminating

Moving a client from Server 2008 to Server 2019 Domain Controller. Which means adding the 2019 server to the domain as a domain controller, promoting it, etc. But the first step is to do DFSRMig – migrate from FRS to DFSR for Active Directory.

DFSRMig – Run this on the old DC

1# To see what state DFS is in
DFSRMig /GetGlobalState

2# Start the migration to DFSR with
DFSRMig /SetGlobalState 1

3# To check on the migration progress
DFSRMig /GetMigrationState – This will take some time: Run this command until it is in a consistent state.

4# Next step is to Redirect
DFSRMig /SetGlobalState 2

5# When that process is complete (check using /GetMigrationState), move to 3
DFSRMig /SetGlobalState 3

The idea is to get to “Eliminated” state – so you can promote the 2019 server to a DC. Well, I found out that when you get it to “Eliminating” – just after running Set Global State 3 – you can go ahead and promote. However… the old DC might get stuck in eliminating.

Now, the interwebs have plenty of suggestions and those should work in most cases – not mine, but most. I was completely baffled… but was restarting the service and looking at event logs… and seeing “access denied” messages. Which was totally weird. I eventually put 7 and 10 together and got 42…

I opened ADSIEdit (as admin) and went to Domain Controllers, expanded those…

After NTFSR is eliminated…

And clicked on the Domain Controller names that was there and went to security… at first, nothing looked off… but eventually, I saw that somebody(!!) had set a Deny on the EVERYONE group so that nobody could delete anything under there – including the NTFSR (NTFRS?) folders! So, of course, it couldn’t eliminate it – access was, in fact, denied. I removed the deny from a couple ‘delete’ permissions and stopped the DFSR service and started it again – Boom! Eliminated!

Company Share Folders and Permissions

Sharing out company information is a very important thing to do right – who gets to see what and what can they do with it? That is why it is best to have a plan in place before beginning.

Plan to have the shares in one folder, on a data drive (not OS), preferably on a dedicated File Server (not a NAS, please). Name the folders something recognizable.

Company is the general share that everyone can access.

Executive is the share for Executives

Accounting is the share for the accounting department – payroll, billing, etc.

Home is a folder for each user’s Home Drive (As set up in Active Directory)

Departments is a top folder with department folders inside, each department folder shared out to it’s department security group.

Ah, yes – Security Groups. You will always want to assign permissions using security groups. It is a lot easier to manage adding users to security groups than going around digging through folders for which ones they need access to. If they need access, there should be a security group for that!

Now, how to map the folders? Group Policy. Put one group policy for each Security Group and which folders they need mapped.

Another very awesome tool for doing group policy is Item-Level Targeting. With this, you can use one policy with multiple items that go to different groups – such as printers, drive maps, etc.

On the Common Tab, Check the Item-level targeting box and click the targeting button…

Item Level Targeting

There are a LOT of options – even though I normally use only the Security Group option – you can get very specific.

Endless Options to target…
The most used (by me) Targeting…

So, if you are deploying dozens of printers or drive mappings – or software – use Item-Level targeting to control who or which pc gets what. It saves clutter in Group Policy management.

Migrated to DreamHost

So, after more than a decade at WebIntellects hosting, I had to call it quits – they just didn’t keep up with my website – WordPress based, but not very active – still, logging in to the CPanel even got laggy. Moved one site to DreamHost and it was a lot faster, more reliable and easier to manage.

Migration, however, was not as straight forward as they would have you believe. Here is the REAL story.

I’m going to assume you have a current website (wordpress) and just set up your account, domain and got wordpress installed at DreamHost. What now?

Stop. You have some information to gather first.

Start by getting your WordPress MySQL credentials… (1) Click on MySQL Databases, (2) scroll down to Database(s) on this server:

(3) Click on the Users Access ( after you copy down the user name! ) and then scroll down to the “Do you need to know user’s password? and click Show – copy it.

Now, go back to MySQL Databases and scroll to HOSTNAME – click on the phpMyAdmin – where you will use the credentials gathered above to log in….

It should auto populate the MySQL Hostname:

Scroll down until you find wp_users (or wp_somthin_users) and expand that table.

Copy the user_login name and click Edit to change the password :

Find user_pass and click the drop down to set it to MD5

Enter a new password and click GO (bottom right)

When you go back to look at it, it will be in a hex string, so make sure you know what the password is or you will have to change it again.

On your DreamHost, under Manage Websites, manage your site and Get Migration Key under WordPress

I already got the Key… 😀

Now you should have:
Dreamhost WordPress admin User name and Password
Migration Key
Current WordPress admin User name and Password.


Now, go to your current wordpress and log in as an admin.

Go to Plugins, Add New and search and install DreamHost Automated Migration.

Activate it.

It will ask you for the information gathered – put it all in and Begin Migration!

Once Migration is complete (15 minutes or more depending on your site size), you are not done. Now you need to point your domain’s DNS to DreamHost.

I use Cloudflare for my DNS, so I get the DNS settings from Manage Websites –> Manage –> DNS Records (under Domain)

Update appropriately – but you MUST have a mydomain.com and a www.mydomain.com record – or a sub.mydomain.com and a www.sub.mydomain.com record.

Once those are done, you need to turn on SSL…

Under Websites, click on Secure Certificates – then Add one to your website:

I went with the Free personal site SSL from Let’s Encrypt….

Once that order processes and is installed – you should see the Lock beside your website is green:

Now you should be able to see your new site (given fast DNS updates), though it may take a bit – 15 minutes or longer depending on your DNS’s TTL settings.

And Migration is complete – go in to word press and update plugins, check your Site Health, etc. Explore your page and make sure its all showing properly.

For References from DreamHost :
https://help.dreamhost.com/hc/en-us/articles/360002208532

https://help.dreamhost.com/hc/en-us/articles/221610868-Finding-your-database-login-credentials

Robocopy Nested “Application Data” Glitch

Wow.

So, I copied some profiles over using Robocopy. The size of these profiles on the new server was staggering! I had to expand the drive to accommodate the bloat!
Then I started looking – the data on the original profiles was not anywhere close to that big.

What happened?

Well, this is my normal robocopy command :

Robocopy \\OldServer\c$\Users\username C:\users\username * /ZB /e /Copy:DATSO /dcopy:DAT /xo /r:0 /XD $Recycle.Bin DFSRPrivate /XF desktop.ini thumbs.db /Log:c:\temppath\username.log /np /tee

One profile got the glitch – and others were starting to before I added a little line in my command code:

Robocopy \denali-rds-s16\c$\Users\username C:\users\username * /ZB /e /Copy:DATSO /dcopy:DAT /xo /r:0 /XD $Recycle.Bin "Application Data" DFSRPrivate /XF desktop.ini thumbs.db /Log:c:\temppath\username.log /np /tee

The profile that had the problem – well, I am still deleting with the following code:

Robocopy c:\temppath\blank "C:\users\username\appdata\local\application data\application data" * /ZB /e /purge /Copy:DATSO /dcopy:DAT /xo /r:0 /XD $Recycle.Bin DFSRPrivate /XF desktop.ini thumbs.db /Log:c:\temppath\usernamePurge.log /np /tee

c:\temppath\blank is an empty folder. Robocopy will delete anything within the target thanks to the /purge switch. This also works for folder paths with more than 256 characters that windows can’t delete on it’s own.

Anyway – starting I had 190 GB free on the drive – I am at 400 GB free on the drive now. More than 200 GB in nested “Application Data” folders – replicated by robocopy over and over and over again.

The Cleanup in Action

24 nested folders – and this is after about 20 minutes of deleting! It is still going as I write this!

… and it just finished – 402 GB free –

Okay, I know what you’re thinking – what does copying a profile to a new system with Robocopy accomplish? It doesn’t really transfer the profile over!

That is where I have to give a shout out to the Genius team at ForensIT – Profile Wiz is a life safer!
http://www.forensit.com/products.html

You can use Profile Wiz to literally take over a profile! Lets say, for example, JohnSmith worked for the company for 5 years, and all his documents and such were in his profile. John gets hit by a bus and you get a new employee – Brad Cooper – well, you want Brad to have all of John’s information – you can use Profile Wiz to give c:\users\johnsmith to the BradCooper login. Concerned about the folder name? Change the folder name to BradCooper – and then use the “Unassigned Profiles” checkbox to assign it to Brad.

Absolutely worth the Profession Edition!

DC AD and Group Policy

In the last post, I covered setting up a new domain controller and some things to help keep your domain healthy, well organized and your IT provider happy.

In this followup, I will keep going. Now that we have a Domain Controller, a Domain and DNS, we should look at Group Policy.

Group Policy Walk-Thru

One of the reasons that we chose to create OUs instead of Containers in the last post/video is that group policy can be applied to OUs, but not Containers.

In going over Group Policy, I’d like to start with User folders. In a corporate environment, losing a file can be a very bad thing. For the most part, servers are backed up, but workstations are not. So, how to protect the files of users? Server Shared folders are one option, but I’ll cover a couple others in this post – Folder Redirection and Home Folders. These let your users have more control over their files, as other users can not normally access either one.

It is a good idea to make a dedicated drive for Data files, separate from the OS drive.

For Home folders, create a folder on the Data drive named something like “HomeFolders.”

Open properties of the folder, security, advanced and disable inheritance.

Remove the Users permissions – give Authenticated users “This Folder Only” permissions to:
List folder / read data
Read attributes
Read extended attributes
Create folders / append data
and
Read permissions

The user “Creator Owner” should have “Subfolders and Files onlyfull control.


On the Sharing tab, use advanced and share the folder as “Home$” to make it a hidden share. Give Everyone read and Authenticated Users full control of the share.

In Active Directory Users and Computers, on the Profile tab, in the Home Folder section, choose a drive letter and put a path with a folder name that matches the user’s logon name.

Clicking Apply creates the folder. If you have a lot of users and don’t want to edit every user to add the home folder, you can use powershell – but you will need to use powershell to give them permissions to the folder as well.
Below is a powershell script to create the folders for existing users, give the users permissions and set the home folder for all users in active directory.

Import-Module ActiveDirectory

#Script for updating folder permissions to give the user full access to their home folder
# as long as its named the same as their username - so, jdoe will have full access to the jdoe folder.
# - with This "Folder, Subfolders and Files" level.
#
# --- change the domain name
 $domain = "kearan"
 $hdpath = "E:\KearanCo\HomeFolders"

# --- Make Home Directories
 $users=get-aduser -filter *
  Foreach($user in $users){
  $usern=$user.samaccountname
  $nhd = $hdpath + "\" + $($usern)
  New-Item -ItemType Directory -Path $nhd
  }

# ---- change the Folder Path
 $folders = Get-ChildItem -Path $hdpath | Where-Object -FilterScript {
     $_.PSIsContainer -eq $true
 }

# --- Set the folder permissions
 foreach ($folder in $folders) 
 {
     $path = $folder.fullname
     $ACL = Get-Acl -Path $path
     $user = $folder.name
     $AccessRule = New-Object System.Security.AccessControl.FileSystemAccessRule("$domain\$user","FullControl",”ContainerInherit, ObjectInherit”,"None",”Allow”)
     $AccessRule1 = New-Object System.Security.AccessControl.FileSystemAccessRule("$domain\Domain Admins","FullControl",”ContainerInherit, ObjectInherit”,"None",”Allow”)
     $Account = New-Object -TypeName System.Security.Principal.NTAccount -ArgumentList "$domain\$user"
     $acl.SetOwner($Account)
     $acl.SetAccessRule($AccessRule)
     $acl.SetAccessRule($AccessRule1)
     $acl | Set-Acl $path
 }

# --- Set users Home Directory in AD ---
# --- change "FileServer" to the actual file server name 
# --- and Home$ to the actual share name. And H to your share letter.
 $users=get-aduser -filter * 
  Foreach($user in $users){
  $usern=$user.samaccountname 
  $HomeDir="\\FileServer\Home$\$($usern)" -f $usern
  Set-ADUser $user -HomeDirectory $HomeDir -HomeDrive H:
  }

You can use each section of the above script as a stand-alone script in order to do one at a time. Use the below code to change an existing home drive to a new server.

# Change Home Directory
$users=get-aduser -filter {homedirectory -like '*Old_Server*'} 
 Foreach($user in $users){
 $usern=$user.samaccountname 
 $HomeDir="\\NewServer\Home\$($usern)" -f $usern
 Set-ADUser $user -HomeDirectory $HomeDir -HomeDrive H:
 }

For Folder Redirection, create an AD group for all those you want to have redirected folders. Unless you are comfortable having all the users in an OU having the folder redirection, of course. To have more control over what accounts get the folder redirection, use the AD group method.

Create a folder, like the Home Folder above, with the same permissions. Now, go into Group Policy Management and create a new Group Policy.

Edit the group policy and go to User Configuration –> Policies –> Windows Settings –> Folder Redirection

Choose the items to redirect (See the video) and set the scope of the policy to Domain Computers (or whichever computer group you want, such as RDS Servers) and the Group you want to apply it to, ie “Folder Redirection Group.” Apply the policy to the domain, or the target OU.

See the video for more on Group Policy and Troubleshooting.

The GPupdate code from the Video:

gpupdate

gpresult /H e:\kearanit\%username%_GPResult.htm

BONUS:
– Video only available through the blog – how to enable the AD Recycle Bin – restore accidentally deleted user accounts!