Jere's Techblog

Install Consul Hashicorp trough Powershell

The Consul Hashicorp DNS service is a frequently used and modern service. Enclosed you will find a Powershell Script to install the Consul-Client as a Windows-Service. The script also allows you to run a version update.

When using the script you have to adjust the variables in line 1-13. For the update only the version number has to be adjusted, which is based on https://releases.hashicorp.com/consul/.

The script creates and updates the following config Files:
\config\consul.hcl
\config\node_exporter.json

If you don’t need this, you can comment it out of the script yourself.

#Install/Update Consul Service
#21.09.2020   by J.Kühnis

#Consul Version  based on https://releases.hashicorp.com/consul
$CONSUL_VERSION = '1.7.5'
$CONSUL_URL = 'https://releases.hashicorp.com/consul'
$CONSUL_DIR = "$env:SystemDrive\Consul"
$consulfqdn = "fqdn.consul.server.company.example"
$domainfqdn = "fqdn.server.company.example"

# Load DatacenterLoc from Webrequest
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$DatacenterLoc = (Invoke-WebRequest "http://fqdn.consul.server.company.example:8500/v1/kv/config/datacenter?raw=true" -UseBasicParsing).Content


#Windows Service params
$params = @{
  Name = "Consul"
  BinaryPathName = "$CONSUL_DIR\Consul.exe agent -config-dir=$CONSUL_DIR\config -data-dir=$CONSUL_DIR\data"
  DisplayName = "Consul"
  StartupType = "Automatic"
  Description = "Consul Hashicorp DNS Service."
}
#stop consul service, if exists
IF(get-service -Name $params.Name -ErrorAction SilentlyContinue){
    Stop-Service -Name $params.Name -Force
    do {
        Start-sleep 1
    }
    until((Get-Service -Name $params.name).status -eq 'Stopped')
    start-sleep 1
}

#Create Consul Folder
IF(!(Test-Path $CONSUL_DIR)){
    New-Item -ItemType Directory $CONSUL_DIR
}
Set-Location "$CONSUL_DIR"
#Create Subdir
$arraySubDir = @(
"data"
"certs"
"config")

$arraySubDir | %{
    IF(!(Test-Path "$CONSUL_DIR\$_")){
        New-Item -ItemType Directory "$CONSUL_DIR\$_"
    }
}

<# Set Download Proxy, if needed
$WebClient = New-Object System.Net.WebClient
$WebProxy = New-Object System.Net.WebProxy("http://your.proxy.url",$false)
$WebProxy.Credentials = $Credentials
$WebClient.Proxy = $WebProxy
#>
    #down
$WebClient.DownloadFile("${CONSUL_URL}/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_windows_amd64.zip","$CONSUL_DIR\consul_${CONSUL_VERSION}_windows_amd64.zip")
$WebClient.DownloadFile( "${CONSUL_URL}/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_SHA256SUMS","$CONSUL_DIR\consul_${CONSUL_VERSION}_SHA256SUMS")
$WebClient.DownloadFile( "${CONSUL_URL}/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_SHA256SUMS.sig","$CONSUL_DIR\consul_${CONSUL_VERSION}_SHA256SUMS.sig")
get-content "${CONSUL_DIR}/*SHA256SUMS"| select-string  (get-filehash -algorithm SHA256 "${CONSUL_DIR}/consul_${CONSUL_VERSION}_windows_amd64.zip").hash.toLower()

#Unzip Download
Expand-Archive "${CONSUL_DIR}/consul_${CONSUL_VERSION}_windows_amd64.zip" "$CONSUL_DIR" -Force

#Add env var
$env:path += ";${CONSUL_DIR}"
[Environment]::SetEnvironmentVariable("Path", [Environment]::GetEnvironmentVariable("Path", "Machine") + ";${CONSUL_DIR}", "Machine")


#Create/Update ConfigFiles

### Create HCL Config File; consul.hcl ###
$hcl_config = "${CONSUL_DIR}\config\consul.hcl"
IF(Test-Path $hcl_config){
    Remove-Item $hcl_config -Force
}
New-Item $hcl_config

$hcl_filecontent = @(
'datacenter = ' + '"' + $DatacenterLoc + '"'
'retry_join = ["' + $consulfqdn + '"]'
    'domain = "' + $domainfqdn + '"'
'acl = {enabled = true, default_policy = "deny", enable_token_persistence = true }'
)
Add-Content -Path $hcl_config $hcl_filecontent


### Create node_exporter.json config file ###
$json_config = "${CONSUL_DIR}\config\node_exporter.json"
IF(Test-Path $json_config){
    Remove-Item $json_config -Force
}
New-Item $json_config


$body = [pscustomobject]@{
    services= @([ordered]@{
        name = "node-exporter"
        tags = @("monitor")
        port = 9100
    })
}
   
Set-Content $json_config ($body | ConvertTo-Json -Depth 3)

#register Consul as a WindowsService
IF(!(get-service -Name $params.Name -ErrorAction SilentlyContinue)){
    New-Service @params
}
Start-Service -Name $params.Name
Continue reading...

Jobs on PowerShell

Certain processes can take a long time. As an example, if you want to search a specific event log trough several servers.
To counteract this, Powershell has introduced “Jobs, Workflows and Foreach-Parallel”. It should be well estimated what you can use where best. Personally, I like to rely on jobs when it comes to remote querying / remote invocation.

There is a sensational blogpost by Harry Eagles bout the topic:

https://blogs.technet.microsoft.com/uktechnet/2016/06/20/parallel-processing-with-powershell/

I would like to show a small example of how I use Jobs to read out Eventlogs about several machines. In the example, it is only checked if the corresponding log exists or was written in the last 45 minutes. For 128 servers I needed 30 minutes with this parallel Task. I killed the sequential script after 6 hours …

#by J.Kühnis
$Servers = @(
"serverHostname1"
"serverHostname2"
"serverHostname3"
)

Remove-Job *
$outputArray = @()

Foreach ($Server in $Servers){

Start-Job -Name $Server -ArgumentList $Server{
    param($servername)
    IF(Get-EventLog -LogName System -InstanceId '12306' -After (Get-Date).AddMinutes(-45) -ComputerName $servername -ErrorAction Ignore){
    
    Write-Output"$servername  true"
    }Else{
    Write-Output "$servername  false"
    
    }
} 

}

While (Get-Job -State "Running") {
    Clear-Host
    write-host "Jobs Running"  (Get-Job).count
    Start-Sleep 2
}
Clear-Host
Get-Job
write-host "Jobs completed, getting output"

Get-Job | ForEach-Object {
    $a = Receive-Job $_.Id
    $a
    $outputArray += $a
  
}

#Use the variable $outputArray to get or export the Outputdata
Continue reading...

Join Azure VM into AD and install SCCM Client

Enclosed a script to join the Azure machine into AD and install the SCCM client. This is useful if you want to populate a native Azure VM that was not installed with SCCM. To make the AD-join a service user was assigned in the script, certainly not the most beautiful variant but this can be encrypted by a compiled EXE. This can be done with the following tool: https://gallery.technet.microsoft.com/scriptcenter/PS2EXE-GUI-Convert-e7cb69d5

Of course, network access to the AD and SCCM server must be available.

#by Jeremias Kühnis
#02.10.2019

### Vars Section ###
$SCCM_Server = "SCCMPrimary.f.q.d.n"
$site_code = "Your Site Code -like S01"
$SCCM_MPServer = "ManagementPoint.f.q.d.n"
$LocalSource_Path = "$env:SystemDrive\temp\"
$SCCM_ClientInstaller = "$LocalSource_Path" + "Client\ccmsetup.exe"
$SCCM_Repo = "\\$SCCM_Server\SMS_***YOURSITECODE***\Client"
$time = ([datetime]::now).tostring("dd_MM_yyyy_HH-mm-ss")
$ScriptFilename = Split-Path $MyInvocation.MyCommand.Definition -leaf
$LogfileName = $LocalSource_Path + 'Azure-SCCMInstaller' + '__' + $time + '.log'
$fqdn = 'someFQDN'
$JoinADUser = $fqdn + '\ServiceAccountjoinAD'
$JoinADUserPw = 'ServiceAccountPW'
$ADOU_NewAzuewDevice = "OU=Germany,DC=contoso,DC=com"



### External Functions ###
#by   https://gallery.technet.microsoft.com/scriptcenter/Write-Log-PowerShell-999c32d0 


function Write-Log { 
    [CmdletBinding()] 
    Param 
    ( 
        [Parameter(Mandatory = $true, 
            ValueFromPipelineByPropertyName = $true)] 
        [ValidateNotNullOrEmpty()] 
        [Alias("LogContent")] 
        [string]$Message, 
 
        [Parameter(Mandatory = $false)] 
        [Alias('LogPath')] 
        [string]$Path = 'C:\Logs\PowerShellLog.log', 
         
        [Parameter(Mandatory = $false)] 
        [ValidateSet("Error", "Warn", "Info")] 
        [string]$Level = "Info", 
         
        [Parameter(Mandatory = $false)] 
        [switch]$NoClobber 
    ) 
 
    Begin { 
        # Set VerbosePreference to Continue so that verbose messages are displayed. 
        $VerbosePreference = 'Continue' 
    } 
    Process { 
         
        # If the file already exists and NoClobber was specified, do not write to the log. 
        if ((Test-Path $Path) -AND $NoClobber) { 
            Write-Error "Log file $Path already exists, and you specified NoClobber. Either delete the file or specify a different name." 
            Return 
        } 
 
        # If attempting to write to a log file in a folder/path that doesn't exist create the file including the path. 
        elseif (!(Test-Path $Path)) { 
            Write-Verbose "Creating $Path." 
            $NewLogFile = New-Item $Path -Force -ItemType File 
        } 
 
        else { 
            # Nothing to see here yet. 
        } 
 
        # Format Date for our Log File 
        $FormattedDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" 
 
        # Write message to error, warning, or verbose pipeline and specify $LevelText 
        switch ($Level) { 
            'Error' { 
                Write-Error $Message 
                $LevelText = 'ERROR:' 
            } 
            'Warn' { 
                Write-Warning $Message 
                $LevelText = 'WARNING:' 
            } 
            'Info' { 
                Write-Verbose $Message 
                $LevelText = 'INFO:' 
            } 
        } 
         
        # Write log entry to $Path 
        "$FormattedDate $LevelText $Message" | Out-File -FilePath $Path -Append 
    } 
    End { 
    } 
}

### own Functions ###

function Mount-SCCMShare {
    $DriveName = 'SCCMRepo'
    If (!(Get-PSDrive -name $DriveName -ErrorAction SilentlyContinue)) {
        try {
            New-PSDrive -name $DriveName -PSProvider "FileSystem" -Root  $SCCM_Repo -Credential $credential -Scope Script
        }
        catch {
            Write-Log -Path $LogfileName -Level Error -Message "Can not Mount SCCM Share $SCCM_Repo"
            Exit
        }

    
    }
}

Function SCCMClientInstaller {

    ###########################################################################
    ##          SCCM Client Health check and Troubleshooting Script					
    ##          Author: Lokesh Agarwal
    ##          Date: 23-08-2017
    ##	        Input:- SCCM Client path, MP Address, Site Code
    ##	     Edited: Jeremias Kühnis 27.09.2019 -> Added/Modifying some Code and add some Log Warning
    ###########################################################################




    ############################### Main Code ####################################
    $machinename = hostname


    ############################### Check if WMI is working #######################
    if ((Get-WmiObject -Namespace root\ccm -Class SMS_Client -ErrorAction SilentlyContinue) -and (Get-WmiObject -Namespace root\ccm -Class SMS_Client -ErrorAction SilentlyContinue)) {
        Write-Log -Path $LogfileName -Level Info -Message "WMI is OK"
        $WMI_Status = "Working"
    }
    else {
        Write-Log -Path $LogfileName -Level Warn -Message "Try to Repair WMI"
        Stop-Service -Force winmgmt -ErrorAction SilentlyContinue
        cd  C:\Windows\System32\Wbem\
        Remove-item C:\Windows\System32\Wbem\Repository.old -Recurse -Force -ErrorAction SilentlyContinue -Confirm:$false
        rename-Item Repository Repository.old -ErrorAction SilentlyContinue -Confirm:$false
        Start-Service winmgmt 
    }

    ############################# Check if SCCM Client is installed ##################
    If (Get-Service -Name CcmExec -ErrorAction SilentlyContinue) {
        $Client_Status = "Yes"
        Write-Log -Path $LogfileName -Level Info -Message "SCCM Status OK, Client is installed"
	
        ########### Check if services are running ################################
        $CcmExec_Status = Get-Service -Name CcmExec | % { $_.status }
        $BITS_Status = Get-Service -Name BITS | % { $_.status }
        $wuauserv_Status = Get-Service -Name wuauserv | % { $_.status }
        $Winmgmt_Status = Get-Service -Name Winmgmt | % { $_.status }
        $RRegistry_Status = Get-Service -Name RemoteRegistry | % { $_.status }


        if ($CcmExec_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $CcmExec_Status"
            Get-Service -Name CcmExec | Start-Service
        }

        if ($BITS_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $BITS_Status"
            Get-Service -Name BITS | Start-Service
        }

        if ($wuauserv_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $wuauserv_Status"
            Get-Service -Name wuauserv | Start-Service
        }

        if ($Winmgmt_Status -eq "Stopped") {
            Write-Log -Path $LogfileName -Level Warn -message "Try to start Service $Winmgmt_Status"
            Get-Service -Name Winmgmt | Start-Service
        }

	
	
        $MachinePolicyRetrievalEvaluation = "{00000000-0000-0000-0000-000000000021}"
        $SoftwareUpdatesScan = "{00000000-0000-0000-0000-000000000113}"
        $SoftwareUpdatesDeployment = "{00000000-0000-0000-0000-000000000108}"

        #################### check if Scan cycles are working ###################
        $machine_status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $MachinePolicyRetrievalEvaluation
        $software_status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $SoftwareUpdatesScan
        $softwaredeploy_Status = Invoke-WmiMethod -Namespace root\ccm -Class sms_client -Name TriggerSchedule $SoftwareUpdatesDeployment

        if ($machine_status -and $software_status -and $softwaredeploy_Status) {
            $machine_Rstatus = "Successful"
            Write-Log -Path $LogfileName -Level Info -message "Scan cycles are working $machine_Rstatus"
        }
        else {
            $SMSCli = [wmiclass] "root\ccm:sms_client"
            Write-Log -Path $LogfileName -Level Warn -message "Scan cycles are not working, try to repair SMSCLI-Client"
            $repair = $SMSCli.RepairClient()
            
        }

    }
    else {
        ############## Install SCCM Client ###############################
        Write-Log -Path $LogfileName -Level Info -message "Missing SCCM Client, try to start installation"
        &$SCCM_ClientInstaller /mp:$SCCM_MPServer /logon SMSSITECODE=$site_code
        Start-Sleep 5
        DO {
            $ProcessesFound = (Get-Process -name ccmsetup -ErrorAction SilentlyContinue)
            If (($ProcessesFound) -and ($counter -le "90")) {
                Start-Sleep 10
                $counter++
                Write-Host "Still running: $($ProcessesFound)  $counter"
            }
            Else {
                IF ($counter -gt "90") {
                    Write-Log -Path $LogfileName -Level Error -message "Not OK: Try to kill running Process while InstallationProcess is taking more than 15 Minutes"
                    Get-Process ccmsetup | Stop-Process -Force
                    Exit
                }
                Else {
                    Write-Log -Path $LogfileName -Level Info -message "OK:Process ended by SCCM installer."
                }
            }
        } Until (!$ProcessesFound)

    }

    ####################################################################################################
}

function New-folder {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)][string]$folderpath
    )

    If (!(Test-path $folderpath)) {
        New-Item $folderpath -ItemType Directory
    }
}   

function VerifyPrerequisits {
    #Check Powershell Version
    $psversion = $PSVersionTable.PSVersion.Major
    IF ($psversion -ge "5") {
        Write-Log -Path $LogfileName -Level Info -Message "Powershell Version is OK – $psversion"
    }
    Else {
        Write-Log -Path $LogfileName -Level Error -Message "Missing PowerShell or installed Version is to low  – $psversion "
        $Errorcouonter = "1"
    }
    
    #Check .Net FrameWork Version
    $DotNetVersion = (Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\' | Get-ItemPropertyValue -Name Version)
    IF (Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full\' | Get-ItemPropertyValue -Name Release | Foreach-Object { $_ -ge 461814 }) {
        Write-Log -Path $LogfileName -Level Info -Message ".NetVersion ist OK – $DotNetVersion"
    }
    else {
        Write-Log -Path $LogfileName -Level Error -Message "Missing .NetFramework or installed Version is to low  – $DotNetVersion "
        $Errorcouonter = "1"
    }

    #Verify SourcePath
    IF (!(Test-Path $SCCM_Repo) ) {
        Write-Log -Path $LogfileName -Level Error -Message "Can not find SCCM installer Path: $SCCM_Repo"
        $Errorcouonter = "1"
    }

    ##Last Part
    If ($Errorcouonter -eq "1") {
        Write-Log -Path $LogfileName -Level Warn -Message "Installation is not performed because the checked prerequisites are Wrong/Missing. See further up in the log."
        exit
    }
    Else {
        Write-Log -Path $LogfileName -Level Info -Message "Prerequisits are Okay, try to lead trough installation"
    }


}

Function Remove-InstallerLocation {
    $removepaths = @(
        "$LocalSource_Path\Client"
    )
    foreach ($ToDelPath in $removepaths) {
        Remove-Item $ToDelPath -Recurse -Force -ErrorAction SilentlyContinue
    }
}

###################
### Main Script ###
###################
$password = $JoinADUserPw | ConvertTo-SecureString -asPlainText -Force
$credential = New-Object System.Management.Automation.PSCredential($JoinADUser, $password)

### Create C:\Temp Folder, if not Exists for logs ###
New-folder -folderpath $LocalSource_Path


### Join AD-Section ###
#Need to Add credentials and start Job for OU path
$Error.Clear()
Try {
    Add-Computer -DomainName $fqdn -Credential $credential -OUPath $ADOU_NewAzuewDevice -Force -ErrorAction Stop
    
    
}
catch [System.Net.WebException], [System.Exception] {
    Write-Log -Path $LogfileName -Level Error -Message "Failed @ Ad-join Part...maybe ADAccount already exists."
    Write-Log -Path $LogfileName -Level Error -Message $Error
    Exit
}

Mount-SCCMShare
VerifyPrerequisits


### Install SCCM-Client Section ###
Write-Log -Path $LogfileName -Level Info -message "Start-SCCM Repair/ Install Process..."
Copy-Item $SCCM_Repo -Recurse -Destination $LocalSource_Path -Force
SCCMClientInstaller
Write-Log -Path $LogfileName -Level Info -message "End Section: 'Start-SCCM Repair/ Install Process...'"
Write-Log -Path $LogfileName -Level Info -message "Finished all Jobs, if there are any Problems Check if Computer is joined in AD and Check the Execution Policy. Also Check de Install status of SCCM Client."
Write-Log -Path $LogfileName -Level Info -message "Try to clean Up local install Files"
Start-Sleep 3
Remove-InstallerLocation
Write-Log -Path $LogfileName -Level Info -message "Successfully finish..."

#reboot while AD-Join
shutdown -r -f -t 60
Continue reading...

local & remote msu patchfile installation with Powershell / Windows Update Standalone Installer

Windows Update Standalone Installer

The patch installation is done via wusa.exe.

The following print screen shows the parameters of the Windows Update Standalone Installer.

local installation

As far as I know, the patch cannot be installed with native powershell, means we have to address the wusa.exe in powershell. Of course Powershell is a nice way to automate the whole process.

In the following example, the patch is copied from a UNC share and installed locally.

$patchname = "kb13245.msu"

$patchsource = "\\some\unc\path\kb13245.msu"

Copy-Item $patchsource -Destination "$env:SystemDrive\Temp" -Force

wusa.exe "$env:SystemDrive\Temp\$patchname" /quiet

remote installation

I was not able to run wusa.exe remotely, any tests with workflows, Remotepowershell (Invoke-Command CMDLeet) failed. Even triggering a localy copied batch file caused problems. The wusa.exe process was executed with the correct parameters but aborted after a few seconds.
Even with an Invoke-Command and the parameter -Wait it didn’t work.

Probably it’s because you intervene in the system and perform an unauthorized action.

With the PSExec.exe it is possible to start the process remotely.

The principle is very simple, you have to copy the patch to the appropriate target computer. Then PSExec starts a remote process on the target computer and executes the wusa.exe with the corresponding parameters. The wusae.exe must point to the path where the patch was copied.

#16.05.2019 by JKU
$Hotfix = 'kb-12345.msu'
$HostName = 'F.Q.D.N'
$DestinationPath = "\\$Hostname\c$\Temp\"

Copy-Item C:\temp\$Hotfix -Destination $DestinationPath
#Start Process with PSExec.exe
& C:\Temp\PsExec.exe -accepteula -s  \\$HostName wusa C:\Temp\$Hotfix /quiet /norestart

And so you can distribute a patch for multiple computers with a simple iteration.

full remote automation

There are of course several ready-made scripts to install multiple patches on multiple computers. The script which I use in the company environment, I have inserted below. The code does not come from me, but from the following forum post: https://community.spiceworks.com/topic/2054098-silently-install-patches-remotely-and-reboot?page=1#entry-7246666

#by https://community.spiceworks.com/topic/2054098-silently-install-patches-remotely-and-reboot?page=1#entry-7246666

$RootHotfixPath = 'Patches\'
 
$Hotfixes = @('KB3125574_x64.msu')
$Servers = Get-Content 'MachineList.txt'

foreach ($Server in $Servers)
{
    Write-Host "Processing $Server..."

    $needsReboot = $False
    $remotePath = "\\$Server\c$\Temp\Patches\"
    
        if( ! (Test-Connection $Server -Count 1 -Quiet)) 
    {
        Write-Warning "$Server is not accessible"
        continue
    }

        if(!(Test-Path $remotePath))
    {
        New-Item -ItemType Directory -Force -Path $remotePath | Out-Null
    }
    
    foreach ($Hotfix in $Hotfixes)
    {
        Write-Host "`thotfix: $Hotfix"
        $HotfixPath = "$RootHotfixPath$Hotfix"

        Copy-Item $Hotfixpath $remotePath
        # Run command as SYSTEM via PsExec (-s switch)
        & C:\Windows\PsExec -s \\$Server wusa C:\Temp\Patches\$Hotfix /quiet /norestart
        write-host "& C:\Windows\PsExec -s \\$Server wusa C:\Temp\Patches\$Hotfix /quiet /norestart"
        if ($LastExitCode -eq 3010) {
            $needsReboot = $true
        }
    }

    # Delete local copy of update packages
    Remove-Item $remotePath -Force -Recurse

    if($needsReboot)
    {
        Write-Host "Restarting $Server..."
        Restart-Computer -ComputerName $Server -Force -Confirm
    }
}
Continue reading...

Map Share to free Driveletter

With this code snippet a share can be mapped to the next free drive letter.

$share = "\\any\unc\path"
$PSProviderAlphabet = [char[]]([char]'C'..[char]'Z')
$UsedPSProvider = (get-psdrive).Name | Sort-Object
$FreePSProvider = $PSProviderAlphabet | ? {$UsedPSProvider -notcontains $_}

New-PSDrive -Name $FreePSProvider[0] -PSProvider "FileSystem" -Root $share

It is especially useful if you have to work with a driveletter in the code or if the share has to be mapped in the Scirpt and has to be removed at the end of the script.

Continue reading...

Troubleshooting an App-V issue on a PVS image

This blog entry is based on the following, a little bit outdated article:

The problem is unfortunately still current it just brings some changes with Microsoft Server 2016. Therefore I took the liberty to copy some explanations from the “old article”.

Problem Description

During a recent application implementation project, we ran into the following issue. Some App-V applications, which were installed locally in the PVS image, were unable to start or they were throwing various error messages. One of the applications which were showing errors, was MS Office (Word, Excel, Powerpoint) viewer. The error thrown at startup of the application was the following: “The operating system is not presently configured to run this application.”

The App-V client was 5.0 SP2, but App-V 5.0 SP3 was also used as a troubleshooting step. The PVS target devices were configured as such:
Streamed disk
Persistent disk (flush overflow)
Re-installing the application in the image, solved the issue for that particular application. However, we wanted to know the root cause of this issue, so we logged a case at Citrix. This was done because the behavior was not existing on a regular non-streamed VM.
After extensive troubleshooting together with Citrix support we discovered the root cause:
One of the registry keys (HKLM\SOFTWARE\Microsoft\AppV\MAV\Configuration\Packages\<package ID>) containing the PackageRoot value contained a faulty HarddiskVolume number.
This volume was indicated as 3, whereas the streamed disk volume number was 2.

Solution

Correcting HarddiskVolume3 into HarddiskVolume2 did solve the issue.
The reason why the HarddiskVolume3 value got into the registry was because these applications where installed in the image on the template machine. This was the machine from which the initial vdisk was captured. During the installation of the particular App-V applications, the original disk was still attached .


This is issue is definitely not Citrix or PVS related, but can be encountered on such a setup.
To prevent this issue from happening again in the future, the original disk was detached from the template VM.

You can get the disknumber in CMD with “DISKPART -> ListDisk” or PowerShell “Get-Disk”.

My expirience and solution with this issue on Server 2016:

In our Environment we ran into the same problem and we could fix this on Server 2008R2 & Citrix 6.5, PVS 7.15 with the modification of one of the registry keys (with a Startupscript during worker boot). (HKLM\SOFTWARE\Microsoft\AppV\MAV\Configuration\Packages\<package ID>) containing the PackageRoot value contained a faulty HarddiskVolume number.

On Server 2016 (Citrix 7.17) everything was useless. Creating or deleting a disk on the masterimage or changing the RegKey didn’t work. I also didn’t find a way to change the disk volume number.

The problem could only be solved by adjusting the call parameter of the application. If the application is started in the App-V context it works fine.

As an example:

Microsoft InfoPath 2013

C:\ProgramData\Microsoft\AppV\Client\Integration\D24C3BDD-8FAD-44D3-998C-933F8F053682\Root\Office15\INFOPATH.EXE /appvve:d24c3bdd-8fad-44d3-998c-933f8f053682_6b0281c5-bb0b-49fb-b52c-a6651e8ed2ed

filetype associations fix

to fix the filetype associations you need to add the “/appvee:**APPV-ID***” parameter to the registry root class:

HKEY_CLASSES_ROOT\InfoPath.Solution.4\shell\Open\command

“C:\ProgramData\Microsoft\AppV\Client\Integration\D24C3BDD-8FAD-44D3-998C-933F8F053682\Root\Office15\INFOPATH.EXE” /appvve:d24c3bdd-8fad-44d3-998c-933f8f053682_6b0281c5-bb0b-49fb-b52c-a6651e8ed2ed “%1”

Continue reading...

Reset User Profile FatClients

Just run the Script and have some fun while deleting local/remote Userprofiles 🙂

The parameters Username and ComputerName are mandatory.

The parameter -wildcard:$true allows to delete multiple profiles. For example all users with the profile name “John*“.

#by J.Kühnis 
#Code Elements of https://gallery.technet.microsoft.com/scriptcenter/Remove-UserProfileps1-871f57c4
#Run with elevated rights
$currentPrincipal = New-Object Security.Principal.WindowsPrincipal( [Security.Principal.WindowsIdentity]::GetCurrent( ) )
if ( -not ($currentPrincipal.IsInRole( [Security.Principal.WindowsBuiltInRole]::Administrator ) ) )
{
    Write-Host "This script must be executed in admin mode." -ForegroundColor Yellow
    Write-Error "This script must be executed in admin mode." -ErrorAction Stop
    Pause
}

Function Reset-LocalUserProfile {

    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $true)][string]$Username,
        [Parameter(Mandatory = $true)][string]$ComputerName,
        [switch]$IncludeSpecialUsers = $False,
        [switch]$Force = $True,
        [bool]$Wildcard   
)

    IF ($Username -match '\*'){
        IF($Wildcard){
            Write-Warning "wildcard enabled, deletion for multiple users enabled"

        }Else{
            Write-Warning "Username must be unique without wildcard '*'. If you like to use wildcard, please use '-Widlcard `$true' parameter. "
        return
        

        }
    }
        
    
    $profileFounds = 0

    #Region Functions

    #https://www.petri.com/test-network-connectivity-powershell-test-connection-cmdlet
    Function Test-PSRemoting {
        [cmdletbinding()]
     
        Param(
            [Parameter(Position = 0, Mandatory, HelpMessage = "Enter a computername", ValueFromPipeline)]
            [ValidateNotNullorEmpty()]
            [string]$Computername,
            [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty
        )
     
        Begin {
            Write-Host -Message "Starting $($MyInvocation.Mycommand)"  
        } #begin
     
        Process {
            Write-Host -Message "Testing $computername"
            Try {
                $r = Test-WSMan -ComputerName $Computername -Credential $Credential -Authentication Default -ErrorAction Stop
                $True 
            }
            Catch {
                Write-Host $_.Exception.Message
                $False
     
            }
     
        } #Process
     
        End {
            Write-Host -Message "Ending $($MyInvocation.Mycommand)"
        } #end
     
    } #close function

    #Check IF WinRM is OK

    IF (!(Test-PSRemoting -Computername $ComputerName)) {    
        Write-Host -Message "PS Remoting Error, can't reach Connect with WinRM"
        return
        
    }
    

    Try {
        $profiles = Get-WmiObject -Class Win32_UserProfile -Computer $ComputerName -Filter "Special = '$IncludeSpecialUsers'" -EnableAllPrivileges
    }
    Catch {            
        Write-Warning "Failed to retreive user profiles on $ComputerName"
        return
    }

   
    ForEach ($profile in $profiles) {
        try {
            $sid = New-Object System.Security.Principal.SecurityIdentifier($profile.SID)               
            $account = $sid.Translate([System.Security.Principal.NTAccount])    
            $accountName = $account.value.split("\")[1]
            $profilePath = $profile.LocalPath
            $loaded = $profile.Loaded
            $special = $profile.Special
        }
        catch {
            continue
    
        }
            
        If ($accountName.ToLower() -Eq $UserName.ToLower() -Or ($UserName.Contains("*") -And $accountName.ToLower() -Like $UserName.ToLower())) {
      
            #If ($ExcludeUserName -ne [string]::Empty -And -Not $ExcludeUserName.Contains("*") -And ($accountName.ToLower() -eq $ExcludeUserName.ToLower())) {Continue}
            #If ($ExcludeUserName -ne [string]::Empty -And $ExcludeUserName.Contains("*") -And ($accountName.ToLower() -Like $ExcludeUserName.ToLower())) {Continue}

            $profileFounds ++

            If ($profileFounds -gt 1) {Write-Host "`n"}
            Write-Host "Start deleting profile ""$account"" on computer ""$ComputerName"" ..." -ForegroundColor Green
            Write-Host "Account SID: $sid"
            Write-Host "Special system service user: $special"
            Write-Host "Profile Path: $profilePath"
            Write-Host "Loaded : $loaded"
            If ($loaded) {
                Write-Warning "Cannot delete profile because is in use"
                Continue
            }

            If ($Force -Or $PSCmdlet.ShouldProcess($account)) {
                Try {
                    $profile.Delete()           
                    Write-Host "Profile deleted successfully" -ForegroundColor Green        
                }
                Catch {            
                    Write-Host "Error during delete the profile. Maybe the user with you executed the script has no rights or the script was not started with admin rights." -ForegroundColor Red
                }
            } 
        }
    }

    If ($profileFounds -eq 0) {
        Write-Warning "No profiles found on $ComputerName with Name $UserName"
    }
Write-Host '########## START SCRIPT ##########' -ForegroundColor yellow
Reset-LocalUserProfile
}

Reset-LocalUserProfile
Continue reading...

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...