Delegating User/Computer Delegation to Non-domain Admins

Tags

, , , , ,

Last week I came across a situation where a user needed to set up Microsoft SQL Server Reporting Services and was having difficulties in integrating Kerberos to it. He needed to change the options related to account delegation continuously on several user accounts and computer accounts that he was using for this purpose. Since he had no experience with Active Directory I was helping him with his queries and changing these options when needed. After some time, my schedule started to conflict with his troubleshooting. Therefore I decided to give him a quick training on Active Directory basics and delegate the necessary permissions for him so he can continue with his work at his own pace.

From a quick glance, trusting user/computer account for delegation is an option related to each user or computer account. So granting Full control to the target account for the user should work fine. But this was not the case! After the user was granted permissions, he was receiving the error message “The following Active Directory Domain Services error occurred: Access is denied.” Now this is a “super helpful” error message with a lot of information in it, that could direct us to a solution – “Thanks Microsoft”! 😀

Before the user was granted Full control - all options are greyed out due to lack of permissions.

Before the user was granted Full control – all options are greyed out due to lack of permissions.

The options are available after the user was granted Full control for this computer account.

The options are available after the user was granted Full control for this computer account.

The "super helpful" error message received when trying to save delegation options.

The “super helpful” error message received when trying to save delegation options.

A day passed. I tried delegating at the OU level – no success! Since my mind was telling me that there should be a solution I decided to seek help from Google! One of the interesting search results was this post and it directed me to a setting in group policy. Who would think to look that far for a simple thing like this?!!

The delegation settings for user accounts and computer accounts belong to the domain controllers, sort of. Therefore a user needs to have this permission on the domain controllers. When you go to User Rights Assignment section in the Default Domain Controllers Policy (Computer Configuration -> Policies -> Windows Settings -> Security Settings -> Local Policies -> User Rights Assignment), you can find the setting Enable computer and user accounts to be trusted for delegation. By default only the Administrators are granted this permission. Since I did not want to come here and change the GPO every time a user is in need of this permission, I decided to create a security group named “Access.EnableDelegation” and added it to this GPO setting. Once the user was added to this security group, voila! he is now able to change this setting himself and I’ve got a very happy customer. 🙂

The GPO setting

The GPO setting “Enable computer and user accounts to be trusted for delegation”

Adding the new security group to the GPO setting

Adding the new security group to the GPO setting

A few things to note

  • Microsoft cautions on using this permission as it can impersonate clients and use their credentials to gain access to network resources. Therefore, make sure that the users are revoked of this permission once they are done using it.

Misuse of this user right, or of the Trusted for Delegation setting, could make the network vulnerable to sophisticated attacks using Trojan horse programs that impersonate incoming clients and use their credentials to gain access to network resources.

  • It is a best practice to perform custom changes such as this one in a separate GPO rather than in the Default Domain Controllers Policy.
  • Once a user is granted this permission he has access to change delegation options for all user accounts and computer accounts. Be mindful of the scope of this permission. The only workaround for this would be to use “data hiding”. No one can change anything that is not visible to them!
Advertisements

Delegating Microsoft DHCP Server Administration in a Windows Domain, “completely”

Tags

, , , , ,

“What is there to talk about a simple task like this?!” – this question you may have in your mind is actually a valid question on first look. If you’ve ever had the need to delegate the Microsoft DHCP server administration to a person or, say the network team of your organization you might have used the built-in domain local Active Directory security group “DHCP Administrators” pretty easily but does this approach delegate the DHCP infrastructure “completely” from an Active Directory perspective? Not exactly! You might experience the incompleteness of this “delegation” when your network team says that they cannot authorize or unauthorize DHCP servers. So how do we make this “complete”?

The  domain local security group DHCP Administrators

The domain local security group DHCP Administrators

To do this task we need to look a little deeper into the Active Directory (well, as usual 😉 ). The DHCP server metadata is stored in the NetServices compartment in the node Services in the Active Directory Sites and Services.

How to expose the Service node in Active Directory Sites and Services

How to expose the Service node in Active Directory Sites and Services

The metadata are stored in the dHCPClass objects and each authorized DHCP server has a corresponding dHCPClass object. These corresponding dHCPClass objects are created during the authorization process of DHCP servers and are deleted during the process of unauthorization. Now you almost know what to do next! 😉

dHCPClass objects

dHCPClass objects (Note: DhcpRoot is present by default and you shouldn’t meddle with it!)

The delegation of authorization and unauthorization of DHCP servers is two-fold.

  1. Granting permission to create/delete dHCPClass objects.
  2. Granting permission to change all properties of the existing dHCPClass objects.

So, we go into the Security tab of the NetServices node and find the create/delete dHCPClass objects permission or ACL. But wait, where are those ACLs???

Permissions available in Active Directory Sites and Services which does not contain dHCPClass objects

Permissions available in Active Directory Sites and Services which does not contain dHCPClass objects

"Applies to" section available in Active Directory Sites and Services which does not contain dHCPClass objects

“Applies to” section available in Active Directory Sites and Services which does not contain dHCPClass objects

After pulling our my hair for a few minutes I decided to call upon my sweet friend, the “ADSI Edit”! 🙂 The NetServices node is located in the Configuration naming context in Active Directory. (Note: viewing the Configuration naming context using ADSI Edit is not covered here.)

ad-services-security-adsi

Now we are in business! When you go into the security settings of the node NetServices you can see the two ACLs Create dHCPClass objects and Delete dHCPClass objects. You need to select the This object only from the Applies to: section as it is useless to propagate this ACL to the child objects.

The dHCPClass ACLs - yay! :)

The dHCPClass ACLs – yay! 🙂

When we set the two ACLs shown above we have already accomplished the first goal of ours, which is to delegate permission to create/delete dHCPClass objects. The second goal is to delegate permission to change all properties of existing dHCPClass objects. To accomplish this task we need to Allow List Contents, Read all properties, Write all properties, and Delete to the Descendant dHCPClass objects. Of course you can grant Full control but its really unnecessary!

Always remember the principle of least privilege and never grant a permission that is not needed.

Delegation permissions for exisiting dHCPClass objects

Delegation permissions for existing dHCPClass objects

That’s it! But you know, you can make this delegation “perfect” so no one will ever have to worry about this in future. What if you delegate these permissions to a security group, say “DHCP Authorizers” and add the built-in DHCP Administrators domain local security group as a member to it? Pretty nice ha! Now the DHCP Administrators will indeed be the “full” DHCP Administrators after all.

Until next time!

Note: By default, only the Domain Admins and the Enterprise Admins have the privilege to authorize and unauthorize DHCP servers in an Active Directory domain.

Reset CPU ID Masks of VMware vSphere Virtual Machines

Tags

, , , , ,

It was 2013, a warm sunny day in Colombo and another busy day in my virtual world. We just upgraded all our VMware hosts to new hardware. With new hardware comes new features that can be made available for the virtual machines hosted in them. One of those features that almost always arrives with new hardware is new CPU capabilities. When the hardware is upgraded the CPU Identification Masks should be reset or “refreshed” to match the new host CPU register values so that incompatible values are removed to prevent vMotion issues when the EVC mode is changed to the latest level. As you might already know, upgrading the EVC mode to the newer levels exposes more CPU features to the virtual machines. So you should definitely do it unless there is a very good reason to do otherwise. In my opinion this whole refresh process should be automatic but who am I to shout at VMware. 😉 When we performed the initial random check on our virtual machines’ CPU ID Masks the VMs we found sometimes had the identification masks set and sometimes not. There was no consistency across the virtualization environment. Once we ran our script we noticed that only around 20% of the total number of VMs actually had any CPU ID Mask configuration set and this was never set by us. I’m not sure of the exact reason behind this difference in configuration of the CPU ID Masks of these VMs and why these (or some) ID masks “stick” for the lifetime of a VM. My guess is that these CPU ID Masks are somehow set automatically during some events and the resulting mask depends on the hardware of the host that the VM resides during the time. The task of resetting the CPU ID Mask is simple as clicking a button in the vSphere client but is unimaginably complex when it comes to scripting it. There was no easy way of doing this and not even a clue to reach my goal even remotely with PowerCLI. The one that came to the rescue was one of the VMware Flings – Onyx. VMware Flings available at the VMware Labs has a lot of powerful nifty tools that can ease a vSphere admin’s life.

Onyx is a standalone application that serves as a proxy between the vSphere Client and the vCenter Server. It monitors the network communication between them and translates it into an executable PowerShell code.

So once I used Onyx to monitor and retrieve what the vSphere client actually sends to the vCenter server when we reset the CPU ID Mask of a VM the task was pretty straight forward. Playing with the vCPU’s core was frightening for us as this was our first time doing it and there was no much documentation of it online as well. Since we were performing this task on the production VMs we wanted the task to be more than perfect to avoid any angry customers. So we decided to contact the VMware support. I won’t go into the detail here but the VMware support took a relatively long time to respond to us with a clear answer on how we needed to perform this task and whether we were on the right track in doing it. Basically from their responses it seemed that they were new to this task as us. 🙂 Finally they confirmed that what we did in our script to reset the CPU ID Mask was correct. So we decided to proceed with it. Before running it I decided to add extensive logging so I can revert changes in case something goes horribly wrong. We executed the script from the vCenter servers during a maintenance weekend and it worked like a charm. Time to grab a cold beer!


Update: I have noticed that WordPress messes up the PowerShell syntax in the script. Please use this link to download the script directly.


The code:

# ========================================================================
# name		: reset_cpu_id_mask_to_default.ps1
# author	: Nimantha Wickremasinghe
# version	: 1.0
# purpose	: Reset CPU ID Masks to default values.
# requirements  : PowerCLI
# ========================================================================

# ------- Get_Current_User -------
$script:currentUser = $env:userprofile.split('\')[2]
$script:logPath = "C:\$currentUser\Script_Logs\"

# ------- Get_All_Powered_Off_VMs -------
Function Get-PoweredOffVMs {
	return Get-VM | ? {$_.PowerState -eq 'PoweredOff'}
}

# ------- ReconfigVM_Task -------
Function Reset-vCpuMask($vm) {
	try{
	    $vmConfigSpec = new-object VMware.Vim.VirtualMachineConfigSpec
	    $featureMask = new-object VMware.Vim.VirtualMachineCpuIdInfoSpec
	    $featureMask.Operation = "remove"
		$featureMask.info = new-object VMware.Vim.HostCpuIdInfo
		$featureMask.info.level = 0x80000001
		
	    $featureMaskAMD = new-object VMware.Vim.VirtualMachineCpuIdInfoSpec
	    $featureMaskAMD.Operation = "remove"
	    $featureMaskAMD.info = new-object VMware.Vim.HostCpuIdInfo
		$featureMaskAMD.info.level = 0x80000001
	    $featureMaskAMD.info.vendor = "amd"
		
	    $vmConfigSpec.CpuFeatureMask = $featureMask,$featureMaskAMD

		$view = get-view $vm.id
	    $view.ReconfigVM_Task($vmConfigSpec)
		
		Log-Summary ($vm.Name +": Set CPU ID Masks to default ------- OK")
	}
	catch {
		Log-Failed ($vm.Name + ': ------- FAILED! --- ' + $Error[0])
	}
}

# ------- Backup_OldvCpuMask -------
Function Backup-OldvCpuMask($vm) {
	$bkpLogPath = "$logPath\Backup\"
	$bkpFileName = $vm.Name
	
	try{
		[array]$CpuFeatueMask = (Get-View $vm.id).Config.CpuFeatureMask
		
		if($CpuFeatueMask) {
			if (!(Test-Path $bkpLogPath)) {
				New-Item -ItemType directory -Path $bkpLogPath
			}
			try {
				$CpuFeatueMask >> $bkpLogPath"$bkpFileName.txt"
				Log-Summary ($vm.Name +": Back up old CPU ID Masks ------- OK")
			}
			catch {
				Log-Summary ($vm.Name +": Back up old CPU ID Masks ------- FAILED!")
			}
		}
		else {
			Log-Summary ($vm.Name +": Back up old CPU ID Masks --- (Masks Unset. Nothing to backup.) ------- OK ")
		}
	}
	catch {
		Log-Failed ($vm.Name + ': ' + $Error[0])
	}
}

# ------- Logging -------
Function Log-Failed ($logEntry) {
	$ErrorActionPreference = 'Stop'
	
	if (!(Test-Path $logPath)){
		New-Item -ItemType directory -Path $logPath
	}
	try{
		Add-Content $logPath'Failed.log' "$logEntry"
	}
	catch{
		Write-Host $Error[0] -ForegroundColor Red
	}
	Write-Host "$logEntry" -ForegroundColor Yellow
}

Function Log-Summary ($logEntry) {
	$ErrorActionPreference = 'Stop'
	$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
	
	if (!(Test-Path $logPath)) {
		New-Item -ItemType directory -Path $logPath
	}
	try {
		Add-Content $logPath'Summary.log' "$timestamp # $logEntry"
	}
	catch {
		Write-Host $Error[0] -ForegroundColor Red
	}		
	Write-Host "$timestamp # $logEntry" -ForegroundColor Cyan
}

# ------- Main -------
$choice = Read-Host `n'Start the script? [y/n]'
if ($choice -eq 'y') {	
	do {
		$vc = Read-Host `n'Specify the vCenter Server to connect [name/IP]'
		if ($vc) {
			if (Test-Connection -ComputerName $vc -Count 2 -Quiet -ErrorAction Stop) {
				asnp VMware.VimAutomation.Core -ErrorAction SilentlyContinue
				
				Log-Summary ($vc + ' available')
				Log-Summary ('------- Script Started -------')
				Log-Summary ('Connecting to ' + $vc + '...')
				try {
					Connect-VIServer $vc -ErrorAction Stop
					Log-Summary ('Connect to ' + $vc + ' ------- OK')

					Log-Summary ('Retrieving powered off VMs...')
					[array]$pwrOffVms = Get-PoweredOffVMs
					$pwrOffVmCount = $pwrOffVms.count
					Log-Summary ("Retrieved $pwrOffVmCount VMs ------- OK")
					
					foreach ($vm in $pwrOffVms) {
						if ((Get-VM -Id $vm.id).PowerState -eq 'PoweredOff') {
							Log-Summary ($vm.Name + ': Task execution ------- START')
							Backup-OldvCpuMask ($vm)
							Reset-vCpuMask ($vm)
							Log-Summary ($vm.Name + ': Task execution ------- END')
						}
						else {
							Log-Failed ($vm.Name)
							Log-Summary ($vm.Name + ': ------- FAILED! --- PowerState: ON')
						}
						Log-Summary ('------- ########## -------')
					}
					Disconnect-VIServer $vc -Confirm:$false -ErrorAction SilentlyContinue
					Log-Summary ('Disconnect from ' + $vc + ' ------- OK')
				}
				catch {
					Log-Summary ($Error[0])
				}
				Log-Summary ('------- Script Completed -------')
			}
			else {
				Log-Summary ($vc + ' unavailable!')
			}
		}
		else {
			Write-Host `n'Please enter a valid name/IP for the vCenter Server!' -ForegroundColor Red
		}
		$choice2 = Read-Host `n'Do you want to specify another vCenter Server to connect? [y/n]'
	} while ($choice2 -eq 'y')
	
	Write-Host `n"Logs can be found at $logPath" -ForegroundColor Green
	
	Write-Host `n`n"Press any key to continue ..."
	$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
}
else{
	exit
}

Delegating DFS to Non-domain Admins

Tags

, , , , , ,

Last week I came across a company where its IT personnel is divided into teams based on specific services that the IT department offers the customers. Usually the Distributed File System (DFS) is managed by the person or the group of people who manages the Active Directory. However, in this scenario there is a different team to manage DFS, who is not responsible for managing the Active Directory.

At the time I started working with this, the practice has been for years is that, if a delegation cannot be easily done using the delegation wizard, a person was granted the Domain Admin privileges (or at least that was how it seemed). This needed to be changed!

When you open the DFS Management MMC console, the delegation is pretty straight forward. You can delegate separately for Namespaces and Replication groups using the Delegate Management Permission… option in the Actions pane.

DFS namespace delegation DFS Replication Groups delegation

This seemingly simple task becomes complicated when you have many replication groups as the delegations should be set for each group individually. In addition to this painstaking task, the delegations will need to be done for each new namespace and replication group individually as the delegations are not inherited. For a lazy guy like me, this was unacceptable! 🙂 It was time to dig deeper to find the gems!!

The DFS configuration metadata is stored in the containers Dfs-Configuration and DFSR-GlobalSettings in Active Directory. These containers can be found under the container System. The container Dfs-Configuration holds the DFS Namespace metadata in fTDfs objects and the container DFSR-GlobalSettings holds the DFS Replication metadata in msDFSR-ReplicationGroup objects.

DFS metadata containers

DFS metadata containers

At this point you can be creative on how you want to grant privileges. For my task I just needed to delegate the full control of DFS to the DFS team. So in the security settings of these two containers I added an ACL to allow Full Control for This object and all descendant objects to a new security group named “DFS Admins”. That’s all there is to it! Now when I go to the Delegation tab of a replication group, I can see the DFS Admins group has an ACL that is Inherited. A similar ACL will be there for the namespaces as well.

New inherited ACL

New inherited ACL

Delegating Microsoft NPS Administration, the non-standard way

Tags

, , , , , , ,

If you found this post, then you probably have searched to the ends of the Earth and the cyber space for a solution to this requirement. I know the frustration and I hope I have a solution for you!

My story starts in an organization with a Microsoft infrastructure where the Microsoft Network Policy Servers (NPS) are deployed on the domain controllers as recommended by Microsoft. The domain controllers are managed by the Directory Services team and the NPS, being a network service is managed by a separate Network Services team. Microsoft probably didn’t think of a scenario where the NPS is needed to be managed by a non-domain administrator before they publicly announced their recommendation on deploying the NPS. (It’s Microsoft, nothing surprising there 😉 ). When I started this work we already had more than 10 domain admins which is fairly high. There was only one person in the Network Services team and he was a member of the Domain Admins group since he was the one who managed NPS. The situation became complicated with the arrival of a new member to the Network team.

For NPS administration, you should be a member of the local administrators group of the server on which the NPS role is installed. In the recommended setup, the user should be a member of the domain controller’s Administrators group. There are 4 highly privileged groups in an Active Directory domain; Administrators, Enterprise Admins, Domain Admins, and Schema Admins. These groups are also called Protected Groups. (There are a few other Protected Groups as well). Out of these 4 groups, Administrators group is the most privileged group by design. For example, in a situation where the Administrators group is denied full control of an Active Directory object, a member of this group can “still” take ownership of that particular object and reset all the ACLs of it. Pretty neat ha!

In delegating NPS we have to use the Administrators group whether we like it or not. What we can do however is to reduce the undesired “impact” of this situation. We need to avoid using the groups Domain Admins and Enterprise Admins as they allow “heavenly powers” such as promotion and demotion of domain controllers. So the concepts to follow here are,

  1. enforcing the principle of least privilege, and
  2. preventing privilege escalation.

We have already covered the first one, where we decided to use the Administrators group instead of any other privileged group. Once a user is a member of the Administrators group he/she can freely add him/herself to Domain Admins or Enterprise Admins as he/she needs. The solution, we remove “write members” property on the groups Domain Admins and Enterprise Admins for Administrators right? Unfortunately this simple solution work only for the next ~60 minutes from the moment the change is done. In the built-in mechanics of Microsoft Active Directory, there is a process called SDProp that resets the ACLs of the Protected Groups every 60 minutes. (You can read more about this in the TechNet magazine and the AskDS TechNet blog). Now this is a dead end! We need to grant the Network team access to NPS to perform their day to day work but at the same time we don’t want them to be able to be Domain or Enterprise Admins as they please.

If they can’t see the groups Domain Admins, Enterprise Admins, Schema Admins, and Administrators (of course this one too, so that they can’t make one of their friends an Administrator 🙂 ) they can’t do anything to them right? How can someone change the membership of a group that they can’t see or access in the first place, right?! The strategy I followed here is called “data hiding in Active Directory”. I haven’t seen this technique being widely used but it is indeed a valuable feature. So I created a new group for NPS administration, let’s say “NPS Admins”, added it as a member of the Administrators group. Then I created a new OU named “Protected Objects” at the root of the directory,

  • denied Full Control on it for the “NPS Admins”,
  • disabled permission inheritance,
  • removed Administrators, Authenticated Users and other non-important ACLs, and
  • made sure that the Enterprise Admins, Domain Admins, SYSTEM, ENTERPRISE DOMAIN CONTROLLERS retain the permissions they need on it.

Hang on, we are almost there! Finally I moved the groups Domain Admins, Enterprise Admins, and Schema Admins into the new OU. Unfortunately, the group Administrators being a built-in one it cannot be moved. So I had to add an ACL on the Builtin OU that denied List Contents for the NPS Admins. That’s it, right? Not exactly! What if the new network guy decides to reset the passwords of all Domain Admins and Enterprise Admins one day?! Pretty scary right?! We need to protect them as well. So I moved the users that are members of these two groups into the Protected Objects OU. We have now delegated NPS administration in “safer way” without much compromise.

Protected Objects OU

Protected Objects OU

View of the Protected Objects OU for a NPS Admin

View of the Protected Objects OU for a NPS Admin

View of the Builtin OU for a NPS Admin

View of the Builtin OU for a NPS Admin

You need to make sure that any ACLs that grants “allow” permissions on the OU’s that contain user accounts and groups that are sensitive to your business are removed. Manage these permissions with custom access groups. Something to keep in mind in working with ACLs is that, although the deny ACLs always override the allow ones, in Microsoft Active Directory an allow ACL closest to the target object will override a deny one that is farthest to it. In other words, a non-inherited allow ACL will override an inherited deny one.

Note: If you go deep into modifying the AdminSDHolder you can be creative and set up ACLs on the Protected Groups and accounts according to your requirements but think twice before over complicating your set up. It might be a nightmare during troubleshooting in future, if not for you, then for the next domain administrator. Modifying the AdminSDHolder will not give any solution in delegating the NPS administration. You can read more about this in the Laura’s nice blog post here.

A Quick Report of GUID to Name Mappings of GPOs for the Busy SYSVOL Admins

Tags

, , , ,

A few weeks back, I needed to find the physical files or the folders of a few Group Policy Objects and I had to do the laborious task of navigating to each GPO in the Group Policy Management Console, navigating to the Details tab, copying the GUID, and searching for the GUID in the SYSVOL directory to find them. I could have done this within a few minutes if I already had the GUID to Name mappings of these GPOs. I could have quickly retrieved this each time I wanted by a PowerShell command but to do this I still need to run this command manually each time. So why not automate it?!!

So I came up with the following script to automate this task and created a scheduled task to run it daily. I also thought of preserving the mappings for some time so I can have a “sort of” timeline of creation and deletion of GPOs. So the script keeps a GUID to name mapping records for a single week before it overwrites the whole report on Monday. This was the simplest way to do this to fulfill my requirement. If you need to extend the number of days to retain the records you just need to think a bit more. 🙂

Note: You need to replace domainname.com with your domain name before you run the script. You can use the Get-ADDomain cmdlet if you want to automate this part too.

The generated report will be similar to the following one and will be stored in your Policies directory.

GPO report

Please download the script from here as WordPress may have messed up the PowerShell syntax below.

The code:

# ==============================================================================================
# name    : get_gporeport.ps1
# author  : Nimantha Wickremasinghe
# purpose : Generate a GPO report with Guid-Name mapping.
# version : 1.0
# ==============================================================================================

Import-Module GroupPolicy -ErrorAction Stop

$hostname = [Environment]::MachineName
$date = Get-Date
$rptFile = "\\"+ $hostname +"\sysvol\domainname.com\Policies\GPOReport.txt"

$rpt = "$date"
$rpt += Get-GPO -All | Select Id, DisplayName | Format-Table -AutoSize | Out-String -Width 10000
$rpt += "===================================================================`n"

if ($date.DayOfWeek -ne "Monday") {
    # Overwrites the file on Monday.
    # Guid-Name history is kept for 7 days in the report.
    if (Test-Path -Path $rptFile) {
        $curData = Get-Content -Path $rptFile | Out-String
    }
}

$rpt | Out-File $rptFile -Encoding ascii -Width 10000
# Without ASCII encoding Add-Content adds funny characters.

# Append the old data
if ($curData) {
    Add-Content $curData -Path $rptFile
}
exit

Reporting VMware vCPU Usage Statistics for a Given Period of Time

Tags

, , ,

Some time back I was managing a VMware vSphere environment that was spread across several regions with more than two thousand virtual machines. In a very dynamic virtualised environment such as this one the virtual machines are sized frequently due to varying needs. After some time the VMs are sometimes allocated to different projects or decommissioned or sometimes the sizing was for a short term. These VMs that now have reduced workloads are over provisioned and one of the main resource that is over provisioned this way is the number of vCPUs. To maintain an efficient infrastructure it is a good practice to right size virtual machines from time to time.

The requirement I had this time was to right size virtual machines’ vCPU configurations. In this situation I focused on collecting the vCPU usage statistics of the virtual machines that had more than 2 virtual processors for a period of 30 days. One of the challenges in here was to limit the statistics to the working hours only so that I have a more accurate report on the work hour workload on them. Although I have laid the criteria to generate the report easily there was no option in VMware vSphere management console to collect this kind of a customized data set. Time to get into PowerCLI – yay! 🙂

Finally I ended up in writing the following script. It allows the user to specify the number of days from the day of the script is run to search for statistics during the runtime and generate a user friendly HTML report using the statistics. The names of the virtual centers should be specified in the variable $VCs and if the minimum number of vCPUs that you need to check is different than 2 then the value can be changed in the integer variable $numCpu.

Please download the script from here as WordPress may have messed up the PowerShell syntax below.

# =============================================================================
# name		: get_vcpu_usage_stats.ps1
# author	:NimanthaWickremasinghe
# version	: 1.0
# purpose	: Retrieves the CPU usage statistics ofVMs for a given period.	
# requirements	:PowerCLI
# =============================================================================asnp VMware.VimAutomation.Core-ErrorActionSilentlyContinue

[double]$statHistory = Read-Host "Please enter the duration you want to check for statistics in number of days"-ErrorActionSilentlyContinue

if($?-and ($statHistory-gt 0)){
	$date = Get-Date
	$today = Get-Date-Formatd-M-yyy
	$rptPath = "C:\vm_cpu_stat_all_vms_$today.html"
	$VCs = "vCenter1","vCenter2","vCenter3"
	$sTimeWH = "08:00:00"
	$fTimeWH = "17:00:00"
	$sTimeNWH = "17:00:01"
	$sTimeNWH = "07:00:00"
	[int]$numCpu = 3
	
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= ''
	$rptHeader+= "<h2><span style="color: purple; font-family: Helvetica;">Virtual Machine vCPU Usage </span></h2>"
	$rptHeader+= "<h3><span style="font-family: Helvetica;">Period: " + (Get-Date).AddDays(-$statHistory).tostring().split(",")[0] +" - "+ (Get-Date).tostring().split(",")[0] + "</span></h3>"

	foreach ($vc in $VCs){
		Connect-VIServer $vc

		$vms = Get-VM | ?{$_.NumCpu -ge $numCpu}
		
		$rptBody += "<span style="color: green; font-family: Helvetica;"><b><span style="text-decoration: underline;">" + $vc.toUpper() + "</span>: "+ $vms.count +" VMs</b></span>

"
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''
		$rptBody += ''

		foreach ($vm in $vms){
			$chkDate = -$statHistory
			
			$cpuStat = $vm | Get-Stat -ErrorAction SilentlyContinue -Start $date.AddDays(-$statHistory) -Finish $date -Stat cpu.usage.average | Measure-Object -Property Value -Average -Maximum -Minimum
			
			do{
				$sTimeStampWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $sTimeWH
				$fTimeStampWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $fTimeWH
				$sTimeStampNWH = $date.AddDays($chkDate).tostring().split('')[0] +' '+ $sTimeNWH
				$fTimeStampNWH = $date.AddDays($chkDate+1).tostring().split('')[0] +' '+ $sTimeNWH
				
				$cpuStatWH += ($vm | Get-Stat -ErrorAction SilentlyContinue -Start $sTimeStampWH -Finish $fTimeStampWH -Stat cpu.usage.average | Measure-Object -Property Value -Average).Average
				$cpuStatNWH += ($vm | Get-Stat -ErrorAction SilentlyContinue -Start $sTimeStampNWH -Finish $fTimeStampNWH -Stat cpu.usage.average | Measure-Object -Property Value -Average).Average
				
				$chkDate++
			} while ($chkDate -le 0)
			
			$os = ($vm | Get-View).Summary.Guest.GuestFullName
			if(!$os){
				$os = "<i>"+ ($vm | Get-View).Summary.Guest.ToolsStatus + "</i>"
			}
			
			if($cpuStat){
				if($cpuStat.Average -ge 1){$avgCpu = [Math]::Round($cpuStat.Average)}else{$avgCpu = "{0:N1}" -f $cpuStat.Average}
				if($cpuStat.Maximum -ge 1){$maxCpu = [Math]::Round($cpuStat.Maximum)}else{$maxCpu = "{0:N1}" -f $cpuStat.Maximum}
				if($cpuStat.Minimum -ge 1){$minCpu = [Math]::Round($cpuStat.Minimum)}else{$minCpu = "{0:N1}" -f $cpuStat.Minimum}
			}
			else{
				$avgCpu = "<i>No data</i>"
				$maxCpu = "<i>No data</i>"
				$minCpu = "<i>No data</i>"
			}
			
			if($cpuStatWH){
				$avgCpuWH = $cpuStatWH/$statHistory
			
				if($avgCpuWH -ge 1){
					$avgCpuWH = [Math]::Round($avgCpuWH)
				}
				else{
					$avgCpuWH = "{0:N1}" -f $avgCpuWH
				}
			}
			else{
				$avgCpuWH = "<i>No data</i>"
			}
			
			if($cpuStatNWH){
				$avgCpuNWH = $cpuStatNWH/$statHistory
			
				if($avgCpuNWH -ge 1){
					$avgCpuNWH = [Math]::Round($avgCpuNWH)
				}
				else{
					$avgCpuNWH = "{0:N1}" -f $avgCpuNWH
				}
			}
			else{
				$avgCpuNWH = "<i>No data</i>"
			}
			
			$rptBodyTop += ''
			
			Clear-VariablecpuStatWH,cpuStatNWH,cpuStat,maxCpu,minCpu,avgCpu,avgCpuWH,avgCpuNWH,chkDate
		}
		$rptBody += $rptBodyTop
		$rptBody += "<table border="1">
<tbody>
<tr>
<th><span style="color: midnightblue; font-family: Times;">Name</span></th>
<th><span style="color: midnightblue; font-family: Times;">PowerState</span></th>
<th><span style="color: midnightblue; font-family: Times;">NumCPU</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">MaxCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">MinCPU (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU WH(08:00-17:00) (%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">AvgCPU NWH(17:00-07:00)(%)</span></th>
<th><span style="color: midnightblue; font-family: Times;">OS</span></th>
</tr>
<tr>
<td><span style="font-family: Tahoma;">"+ $vm.Name.toLower() +"</span></td>
<td><span style="font-family: Tahoma;">"+ $vm.PowerState +"</span></td>
<td><span style="font-family: Tahoma;">"+ $vm.NumCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $maxCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $minCpu +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpuWH +"</span></td>
<td><span style="font-family: Tahoma;">"+ $avgCpuNWH +"</span></td>
<td><span style="font-family: Tahoma;">"+ $os +"</span></td>
</tr>
</tbody>
</table>"
		
		Disconnect-VIServer $vc -Confirm:$false
		Clear-Variable rptBodyTop
	}
	$rptBody += "<i> * No data </i> - Statistics are unavailable for the given period."
	$rptFooter += ''

	$rpt = $rptHeader + $rptBody + $rptFooter
	$rpt | Out-File $rptPath

	$execTime = ((Get-Date)-$date)
	Write-Host `n"Report generated in (h:m:s): $execTime" -ForegroundColor Yellow
	Write-Host Please find the report at $rptPath -ForegroundColor Green
} else{
	Write-Host `n"Invalid data. You have entered either a non-numeric value or value less than 1." -ForegroundColor Red
	Write-Host `n"Press any key to continue ...`n" -ForegroundColor Yellow
	$x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")

	exit
}

A HTML report similar to the following one will be generated at the end of the execution of the script.

vCPU Usage Statistics Report

vCPU Usage Statistics Report

How to set up remote logins via VNC for Debian, Solaris and Red Hat

Tags

, , , , ,

Debian

Debian Linux (6)

    1. Run the following commands respectively.
apt-get update
apt-get upgrade
apt-get dist-upgrade
    1. Reboot the OS.
reboot
    1. Install the GNOME desktop.
apt-get install gnome-desktop-environment
      • If “apt-get install gnome-desktop-environment” says the packages are missing then ensure “deb http://ftp.us.debian.org/debian squeeze main contrib non-free” is added to the list file.
      • adding the link to sources.list
nano /etc/apt/sources.list

Paste “deb http://ftp.us.debian.org/debian squeeze main contrib non-free” without quotes.

    1. Install required fonts for the VNC server GNOME session.
apt-get install xfonts-100dpi
apt-get install xfonts-100dpi-transcoded
apt-get install xfonts-75dpi
apt-get install xfonts-75dpi-transcoded
apt-get install xfonts-base
    1. Install VNC.
apt-get install vnc4server
    1. Run VNC server manually and set up a password.
vncserver
    1. Making VNC server start at system start up.
      • Run the following command to create the vncserver file.
nano /etc/init.d/vncserver
    1. Copy the following script, paste, and save.
      • Replace “my-vnc-server” with the hostname and keep with quotes. (Optional)
#-----Beginning of the script-------------------------------------------

#!/bin/sh -e
### BEGIN INIT INFO
# Provides:          vncserver
# Required-Start:    networking
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:      0 1 6
### END INIT INFO

PATH="$PATH:/usr/X11R6/bin/"

# The Username:Group that will run VNC
export USER="root"
#${RUNAS}

# The display that VNC will use
DISPLAY="0"
# Color depth (between 8 and 32)
DEPTH="16"
# The Desktop geometry to use.
#GEOMETRY="x"
GEOMETRY="800x600"
#GEOMETRY="1024x768"
#GEOMETRY="1280x1024"
# The name that the VNC Desktop will have.
NAME="my-vnc-server"

OPTIONS="-name ${NAME} -depth ${DEPTH} -geometry ${GEOMETRY} :${DISPLAY}"

. /lib/lsb/init-functions 

case "$1" in 
start)
log_action_begin_msg "Starting vncserver for user '${USER}' on localhost:${DISPLAY}"
su ${USER} -c "/usr/bin/vncserver ${OPTIONS}"
;;

stop)
log_action_begin_msg "Stopping vncserver for user '${USER}' on localhost:${DISPLAY}"
su ${USER} -c "/usr/bin/vncserver -kill :${DISPLAY}"
;;

restart)
$0 stop
$0 start
;;
esac

exit 0
#-----End of the script-------------------------------------------------
    1. Grant permissions for everyone to execute the file.
chmod +x /etc/init.d/vncserver
    1. Register the VNCServer service to run at startup.
update-rc.d vncserver defaults
    1. If VNC login shows a gray screen, then run the following command to set permissions for XInit.
chmod 755 /etc/X11/xinit/xinitrc

Solaris 10

Solaris (10)

  1. Download the latest VNC package for Solaris from http://www.realvnc.com/products/download.html
    1. Go to the download location of the file in the terminal and run the following commands.
gunzip vnc-4_1_3-sparc_solaris.pkg.gz
pkgadd –d vnc-4_1_3-sparc_solaris.pkg
    • Solaris 10 is shipped with a basic VNC service mostly configured. This is the procedure to enable it.
    1. Find the VNC service.
svcs -a | grep -i vnc

disabled 13:47:12 svc:/application/x11/xvnc-inetd:default
    1. Enable VNC service.
svcadm enable svc:/application/x11/xvnc-inetd:default
    1. Note that the VNC is broken by default, some changes will be required.
svcs svc:/application/x11/xvnc-inetd:default

STATE STIME FMRI
maintenance 14:22:41 svc:/application/x11/xvnc-inetd:default
    1. Append VNC to the /etc/services.
echo "vnc-root\t5900/tcp\t\t\t# Xvnc" >>/etc/services
    1. Check /etc/services.
tail /etc/services

...
snmpd 161/udp snmp # SMA snmp daemon
vnc-root 5900/tcp # Xvnc
    1. Note, the GNU display manager is not customized yet, and needs correction.
ls -al /etc/X11/gdm/custom.conf

/etc/X11/gdm/custom.conf: No such file or directory
    1. Enable and configure GNU display manager for VNC.
cat >/etc/X11/gdm/custom.conf <<!--
[xdmcp]
Enable=true
[security]
DisallowTCP=false
AllowRoot=true
AllowRemoteRoot=true
!
    1. Check the customization configuration file.
ls -al /etc/X11/gdm/custom.conf

-rw-r--r-- 1 root root 85 Dec 19 14:43 /etc/X11/gdm/custom.conf
    1. Disable and re-enable, and validate the VNC service.
svcadm disable svc:/application/x11/xvnc-inetd:default

STATE STIME FMRI
disabled 14:46:29 svc:/application/x11/xvnc-inetd:default

svcadm enable svc:/application/x11/xvnc-inetd:default

svcs svc:/application/x11/xvnc-inetd:default

STATE STIME FMRI
online 14:46:43 svc:/application/x11/xvnc-inetd:default
    1. Edit /etc/default/login and comment out the following line to permit log on as Root.
# CONSOLE=/dev/console

Redhat

RHEL (Red Hat Enterprise Linux)

If the RHEL installation is licensed then this is not difficult at all. You just need to run

yum install tigervnc-server -y

at a terminal. However this gets complicated when the installation of RHEL is not licensed.

After a considerable number of disappointing hours of searching, testing, and some research I finally found a solution to this. Here I am going to use TigerVNC as the VNC server and RealVNC viewer as the client (you can use any VNC viewer you prefer).

  1. Navigate to the TigerVNC downloads which at the moment of this blog post is hosted as a SourceForge project here  and download the latest build corresponding to the bit version of your Linux installation.
  1. Extract the contents into a suitable location.
  1. Within the extracted contents a folder named bin exists and it contains all the binaries required for the sound operation of VNC server (except the one which as they say is obsolete now). Locate this folder and copy the contents of it to /bin directory in Linux. The following files need to be copied.
    • vncconfig
    • vncpasswd
    • vncserver
    • Xvnc
    1. Execute the command vncsever at a terminal.
vncserver
    • This will require you to set a password for logging in remotely using VNC.
    • This will also create the necessary files required to start a xsession for a particular user when logging in using VNC.
  1. Usually after the above step you are good to go but in some cases you might end up with an error Connection timed out(1006) at the VNC viewer when you try to log in. This is because the host in which the VNC server runs is blocking the VNC communication, i.e. it’s blocking the VNC port 5900. Here you have to allow the port 5900 at the firewall as a trusted port or if security is not a big issue with the Red Hat host then you can simply disable it.