Jere's Techblog

Migration mounted shared Mailboxes from Outlook 2010 to Outlook 2016

During the migration from Server 2008 R2 – Citrix 6.5 to Server 2016 Citrix 7.15, the customer had a special request: that the language settings, printer mappings and Outlook shared mailboxes be transferred to the new Server2016/Office 2016 environment. Of course, we don’t want to copy the full Windowsprofile, instead we want only use the most necessary settings to keep the new profile as clean as possible.
With the printer mappings and language settings there are so far no problems, all this can be found “relatively simply” in the Registry and taken over (Powershell is your friend).

With the Outlook shared mailboxes it was more complicated…we had in this specific case no possibility to get the relation between the assigned shared mailboxes and the users trough Exchange…the Exchange admin told us that you can’t read the relations with the current Exchangeserver settings.

Although you can technically read the members of a shared mailbox with get-mailbox or a similar command.

Probably you could also mount the shared mailboxes automatically to Outlook…if you like…but honestly, that’s not the point. And
i’m not an MS-Exchange professional…😉

You can actually get Outlook profiles/mounted shared mailbox (profiles can be seen under  CMD: “c:\Windows\SysWOW64\control.exe mlcfg32.cpl“)from the user registry. It may not be the best way but it works and offers a small advantage. You migrate only those shared mailboxes which the user has assigned to himself in the current Outlook version. You will not randomly migrate all shared mailboxes to the new Outlook environment just because the user is a member of those.

To cut a long story short…
What needs to be done now to migrate the mounted mailboxes/profiles from Office 2010/Server2008R2 to Office2016/Server2016?
First an export of the Outlook profiles:
HKCU\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles

Watch your step! This cannot be imported easily…because the path has changed ( Thank you Microsoft!).

New path under Office 2016 (Import to these location):
HKCU\SOFTWARE\Microsoft\Office\16.0\Outlook\Profiles

There are a lot of cryptic registry keys but so far the migration and the Outlookprofiles hasn’t caused any problems. Write me in the comments, if you know the specific key’s which are needed to migrate shared mailboxes (so I can make everything a less streamlined and cleaner)…I don’t take this trouble today 🙂

Automation:

#Registry Functions writte by https://administrator.de/user/colinardo/ @ https://administrator.de/forum/powershell-registry-sichern-wiederherstellen-ohne-reg-exe-regedit-exe-367223.html
#Region variables

[string]$Outlookprofile = "$root\Outlookprofile.xml"

#Region RegKey Function
function Export-RegTree([string]$regkey,[string]$exportpath){
    $data = @()
    $createobject = {
        param($k,$n)
        [pscustomobject] @{
            Name = @{$true='(Default)';$false=$n}[$n -eq '']
            Value = $k.GetValue($n)
            Path = $k.PSPath
            Type = $k.GetValueKind($n)
        }
    }
    get-item $regkey -PipelineVariable key| %{
        $key.GetValueNames() | %{$data += . $createobject $key $_}
    }
    gci $regkey -Recurse -Force -PipelineVariable key | %{
        $key.GetValueNames() | %{$data += . $createobject $key $_}
    }
   $data | Export-Clixml $exportpath
}

function Import-RegTree([string][ValidateScript({Test-Path $_})]$xmlfile){
    Import-Clixml $xmlfile | %{
        if (!(Test-Path $_.Path)){md $_.Path -Force | out-null}
        New-ItemProperty -Path $_.Path -Name $_.Name -Value $_.Value -PropertyType $_.Type -Force
    }
}

#region Outlook Profile

function Export-Outlookprofile{
    Export-RegTree -regkey 'HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles' -exportpath $Outlookprofile
    LOG -TEXT "EXPORT : Outlookprofile $Outlookprofile"
}

function Import-Outlookprofile{
 Invoke-Command -ScriptBlock {start-process outlook} 
     sleep 10
    $Replace = Get-Content $Outlookprofile
    $Replace | % {$_.Replace("HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles","HKEY_CURRENT_USER\Software\Microsoft\Office\16.0\Outlook\Profiles")} | Set-Content $Outlookprofile
    Import-RegTree -xmlfile $Outlookprofile
    LOG -TEXT "IMPORT : Outlookprofile"
}

You have to define the $root variable, it can be something like ‘$root = “$env:appdata\Profilmigration7x”‘ or a Network share. If you use the Appdata path, ensure to Copy the data to the “NEW” appdata-path if you use Citrix or another UPM versioning tool.

Continue reading...

Compare AD-GroupMember

Example:

JKCompare-ADGroupMemeber -Group1 GROUPNAME1 -Group2 GROUPNAME2

Optional Parameter:

-IncludeEqual yes

#13.11.2018 Jeremias Kühnis

Function JKCompare-ADGroupMemeber{
[CmdletBinding()]
Param(
    [Parameter(Mandatory=$true)]
    [string]$Group1,
    
    [Parameter(Mandatory=$true)]
    [string]$Group2,

    [Parameter(Mandatory=$false)]
    [ValidateSet("yes", "no")]
    [string]$IncludeEqual
)

IF($IncludeEqual -eq "Yes"){
    diff (Get-ADGroupMember $Group1) (Get-ADGroupMember $Group2) -Property 'SamAccountName' -IncludeEqual
}Else{
    diff (Get-ADGroupMember $Group1) (Get-ADGroupMember $Group2) -Property 'SamAccountName'
}

Write-Host "#############################################" -ForegroundColor Cyan
Write-Host "== This user is in both groups (If option is enabled)."
Write-Host "=> This user is in the second group ($group2)."
Write-Host "<= This user is in the first group ($group1)."
Write-Host "#############################################" -ForegroundColor Cyan
}
Continue reading...

Remove AD-Group on certain Users

This script is very handy in dayli business, when you need to remove multiple users from an AD-Group.

In the userlist the users can be specified with the samAccountName.

Of course there is the possibility to fill “$UserList” variable with a list e.g. a CSV-File. In this case you can Use the function “Import-Csv” which is an out of the Box Powershell feature.

#13.11.2018 by Jeremias Kühnis
#Remove AD-Groupmemership

$ADGroup = "someAdGroupName"

$Userlist = @(
"SamAccountName1"
"SamAccountName2"
"SamAccountName3"
)

$Userlist | % {Remove-ADGroupMember -Identity $ADGroup -Members $_ -Confirm:$false}
Continue reading...

Bulk reboot Server

With this variant, the servers from a list or an array will be restarted sequentially. If a server is not reachable or has problems with the Windows-Remoting-Service, this can lead to long runtimes. It gives you a nice overview where the reboot job worked or not.

With the parameter “-force” the servers will be rebooted even if there is still an active user session.

#13.11.2018 Restart a list/array of Servers through Windows Remoting

$server = @(
"Hostname-Server1"
"Hostname-Server2"
"Hostname-Server3"
)


foreach ($server in $server){
    try{
        Restart-Computer -ComputerName $Server -force
        write-host "Reboot OK $server" -ForegroundColor Green
    }catch{
        write-host "Reboot NOT OK $server" -ForegroundColor yellow
          }

}

This is the parallel way to reboote servers from a list/array as a job using the “Invoke” function.

#13.11.2018 Restart a Liste/Array of Servers through Windows Remoting

$server = @(
"Hostname-Server1"
"Hostname-Server2"
"Hostname-Server3"
)


foreach ($server in $server){
Invoke-Command -ComputerName $Server -ScriptBlock {shutdown -r -f -t 1} -AsJob  
}

With this function you can check if the servers have been restarted. You can also Check the last boot time.

#13.11.2018 Restart a Liste/Array of Servers through Windows Remoting

$array = @()
$server = @(
"HostnameServer-1"
"HostnameServer-2"
"HostnameServer-3"
)

foreach ($server in $server){

    IF($s= New-CimSession -ComputerName $server -ErrorAction SilentlyContinue){
        $array += (Get-CimInstance -ClassName win32_operatingsystem -CimSession $s ) #| select csname, lastbootuptime
    }Else{
        $myObject = [PSCustomObject]@{
            PSComputerName     = $server
            csname     = $server
            lastbootuptime = 'no data retrieved'
            }
        $array += $myObject
    }
}

Function Checkreboottime{

Param(
  [Parameter(Mandatory=$true)]
   [int]$time
)


$TimeNow = Get-Date

$array | % {
    IF(!($_.lastbootuptime -eq "no data retrieved")){
        IF ([dateTime]$_.lastbootuptime.AddMinutes($time) -ge $TimeNow){
            write-host $_.csname $_.lastbootuptime -ForegroundColor Green
        }Else{
            write-host $_.csname $_.lastbootuptime -ForegroundColor yellow
        }
    }Else{
        write-host $_.csname $_.lastbootuptime -ForegroundColor Cyan
        }
    }

}

After calling the script, the function “Checkreboottime” can be used to check which servers have been restarted within a certain time.

Example: Checkreboottime -time 1000

The value 1000 indicates the minutes.

Yellow = Computer has not been restarted for more than 1000 minutes (since the time the script was executed)

Blue= No values could be determined

Green= computer restarted within 1000 minutes (since the script was executed)

Continue reading...

GPUpdate on all Worker Machines

This Script will invoke an “GPupdate /force”  command on all CitrixWorker Machines.

#12.03.2018 Jeremias Kühnis Updates GPO on all Workermachines
#Ensure that you are running this Script on a Citrix DeliveryController, otherwise you have to enter an Adminadress like '(Get-BrokerMachine -AdminAddress "FQDN of your DeliveryController").DNSName'  (modify Line 13)


IF(!(Get-PSSnapin -Name "Citrix.Broker.Admin.V2" -ErrorAction SilentlyContinue)){
    Add-PSSnapin *
        IF(!(Get-Command -Name "Get-BrokerMachine" -ErrorAction SilentlyContinue)){
         Write-Warning "Could not find/load CitrixPSSnapin 'Citrix.Broker.Admin.V2' or the Cmdlet 'Get-BrokerMachine' is not available. Ensure that you are running this Script on a DeliveryController Server."
         return
        }
    }

(Get-BrokerMachine).DNSName | % {
      Invoke-Command -ComputerName $_ -ScriptBlock {gpupdate /force} -AsJob  
}

Get-Job
Continue reading...