Copying a File from Within a Remote Powershell Session

Recently I needed a way to copy a certificate file from within a PowerShell session to another Windows machine without opening a nested PowerShell session.

Here’s my setup:

  • A Windows 10 laptop, from which I’m remoting
  • NC1, a Server 2016 virtual machine I’m remoted into. It’s a member of a domain.
  • HYPERV1, the Server 2016 machine I want to copy a certificate file to. It’s not a member of a domain.

I execute all of the following commands on NC1, the VM I’m remoted into.

Here’s the first thing I tried. The HYPERV1 machine is not a member of a domain, so the following doesn’t work:
$ Copy-Item .\nccert.cer \\hyperv1\c$
Access is denied
+ CategoryInfo          : NotSpecified: (:) [Copy-Item], UnauthorizedAccessException
+ FullyQualifiedErrorId : System.UnauthorizedAccessException,Microsoft.PowerShell.Commands.CopyItemCommand

What about specifying the -Credential parameter? That doesn’t work either.
$ Copy-Item .\nccert.cer \\hyperv1\c$ -Credential hyperv1\administrator
The FileSystem provider supports credentials only on the New-PSDrive cmdlet. Perform the operation again without specifying credentials.
+ CategoryInfo          : NotImplemented: (:) [], PSNotSupportedException
+ FullyQualifiedErrorId : NotSupported

And that error pretty much tells me what I need to do: use the New-PSDrive cmdlet!
$ New-PSDrive -Name H -PSProvider FileSystem -root \\hyperv1\c$ -Credential hyperv1\administrator
$ Copy-Item .\nccert.cer h:\
$ Remove-PSDrive -Name H

Creating a File Share with PowerShell and Windows Server Core

Sometimes you just need to create a file share.

With Windows Server Core, you don’t have all the old GUI tools that we’re all used to. So you have to make do with PowerShell and the old fake DOS prompt. Fortunately, with a little help, it’s pretty easy.

First, create the folder you want to share. In this case, c:\share

Next, modify the ACL to grant the DOMAIN\File Server Admins group full control

$sharepath = "c:\share"
$Acl = Get-ACL $SharePath
$AccessRule= New-Object System.Security.AccessControl.FileSystemAccessRule("DOMAIN\File Server Admins","full","ContainerInherit,Objectinherit","none","Allow")
$Acl.AddAccessRule($AccessRule)
Set-Acl $SharePath $Acl

Finally, create the share and grant everyone full access.
NET SHARE sharename=c:\share  "/GRANT:Everyone,FULL"

Done.

Use PowerShell to Find Citrix ICA Client Versions In Use On a XenApp 6 Farm

In many Citrix environments it’s common to have a large variety of ICA client versions. One thing that sometimes surprises users and IT folks alike is how much of a performance increase can be seen after upgrading an old ICA client. But how do you know which clients need upgrading?

One of the biggest challenges has been deciphering what ICA client version the cryptic “client build number” in a user’s session information translates to. Well thanks to the XenApp 6 PowerShell SDK, we can easily find Citrix ICA client version information in a snap with the following script:

#################################################
# XenApp 6 Client Version Retrieval Script
# Created by Ben Piper
# http://benpiper.com, ben@benpiper.com
#################################################

function convertToVersion($build) {
	switch($build){
		6685 {"13.0"; break}30 {"12.1"; break}6 {"12.0.3"; break}6410 {"12.0"; break}142{"Java"; break}317{"3.0"; break}324{"3.0"; break}330{"3.0"; break}349{"3.0"; break}304{"MAC 6.3"; break}314{"MAC 6.3"; break}323{"MAC 6.3"; break}326{"MAC 6.3"; break}400{"MAC 7.0"; break}402{"MAC 7.0"; break}405{"MAC 7.0"; break}406{"MAC 7.0"; break}407{"MAC 7.0"; break}402{"MAC 7.0"; break}411{"MAC 7.0"; break}500{"MAC 7.1"; break}600{"MAC 10.0"; break}601{"MAC 10.0"; break}581{"4.0"; break}606{"4.0"; break}609{"4.0"; break}614{"4.0"; break}686{"4.0"; break}715{"4.2"; break}727{"4.2"; break}741{"4.2"; break}779{"4.21"; break}730{"wyse1200le"; break}910{"6.0"; break}931{"6.0"; break}961{"6.01"; break}963{"6.01"; break}964{"6.01"; break}967{"6.01"; break}985{"6.2"; break}986{"6.2"; break}1041{"7.0"; break}1050{"6.3"; break}1051{"6.31"; break}1414{"Java 7.0"; break}1679{"Java 8.1"; break}1868{"Java 9.4"; break}1876{"Java 9.5"; break}2600{"RDP 5.01"; break}2650{"10.2"; break}3790{"RDP 5.2"; break}6000{"RDP 6.0"; break}2650{"10.2"; break}5284{"11.0"; break}5323{"11.0"; break}5357{"11.0"; break}6001{"RDP 6.0"; break}8292{"10.25"; break}10359{"10.13"; break}128b1{"MAC 10.0"; break}12221{"Linux 10.x"; break}13126{"Solaris 7.0"; break}17106{"7.0"; break}17534{"7.0"; break}20497{"7.01"; break}21825{"7.10"; break}21845{"7.1"; break}22650{"7.1"; break}24737{"8.0"; break}26449{"8.0"; break}26862{"8.01"; break}28519{"8.05"; break}29670{"8.1"; break}30817{"8.26"; break}31327{"9.0"; break}31560{"11.2"; break}32649{"9.0"; break}32891{"9.0"; break}34290{"8.4"; break}35078{"9.0"; break}36280{"9.1"; break}36824{"9.02 WinCE"; break}37358{"9.04"; break}39151{"9.15"; break}44236{"9.15 WinCE"; break}44367{"9.2"; break}44376{"9.2"; break}44467{"Linux 10.0"; break}45418{"10.0"; break}46192{"9.18 WinCE"; break}49686{"10.0"; break}50123{"Linux 10.6"; break}50211{"9.230"; break}52110{"10.0"; break}52504{"9.2"; break}53063{"9.237"; break}55362{"10.08"; break}55836{"10.1"; break}58643{"10.15"; break}				
		default {$build; break}
	}
}

$clientUsage = @()
foreach($session in Get-XASession -full | where {$_.state -eq "Active" -and $_.protocol -eq "Ica"}) {
	$clientUsage += new-object psobject -Property @{
		User = $session.accountname
		Workstation = $session.clientname
		Client = convertToVersion($session.clientbuildnumber)
	}
}

$clientUsage | sort-object -Property Workstation | get-unique -asstring

The script makes use of build-to-version information gleaned from Nick Holmquist’s ICA Client Build List and the Citrix Client Build List thread on Citrix Forums (Thanks!). The convertToVersion function uses the PowerShell switch command to identify and then return the ICA client version based on the build number. If the key-value pair is not in the list, the build number is returned. All ICA client versions in the list have at least one decimal place to make it easy to distinguish build numbers from client versions.

Download the script here

Instantly Publishing Citrix Apps to Individual Servers Using PowerShell

Sometimes there is a need to publish an application to individual XenApp servers for baselining or troubleshooting purposes. But if you have a lot of published applications in your XenApp 6 farm, this can be a huge hassle. Thankfully PowerShell allows us to quickly and easily take our existing published applications and automatically create individually published apps for each server. We can use Worker Groups to control which servers get an individually published application. Not only that, we can organize the applications neatly by creating separate console and Client folders for each server. I wrote this script to accomplish all of the above using XenApp 6 PowerShell SDK’s powerful features:

$appFolder = "Applications"
$testfolderpath =  "Applications/Test/Single Server Test"
$sourceAppPath = "Applications/Test"	#apps in the root of this path will not be copied
$publishTo = "DOMAIN\GroupWithAccessToIndividualServers"
$targetWorkerGroup = "Main Farm Servers"
$clientFolder = "Test\Single Server Test"
$WhatIfPreference = $false		# Simulate script but do not execute any changes (will yield errors)

# Main Program

$servers = Get-XAServer -workergroupname $targetWorkerGroup
$sourceFolders = Get-XAFolder -folderpath $appFolder -recurse | where {$_ -notmatch $testfolderpath -and $_ -match $sourceAppPath -and $_ -ne $sourceAppPath}

# Create folder for each server
foreach ($server in $servers) {						
	#generate folder name for each server
	$createfolder = $testfolderpath+"/"+$server.servername
	#create folder for each server
	new-xafolder -folderpath $createfolder
	foreach ($folder in $sourceFolders) {
		#get list of apps to copy in each folder
		$appstocopy = Get-XAApplication -folderpath $folder			
		foreach ($app in $appstocopy) {
			## generate name for new app
			$newname = $app.displayname+" "+$server.servername
			$newclientfolder = $clientfolder+"\"+$server.servername
			## get existing accounts to remove			
			$accountsToRemove = ($app | Get-XAApplicationReport).accounts
			## copy application to server's folder		
			$newapp = $app | Copy-XAApplication -folderpath $createfolder
			## publish application to lone server		
			$newapp | set-xaapplication -servernames $server.servername -clientFolder $newclientFolder
			## remove existing accounts
			foreach ($account in $accountsToRemove) {
				$newapp | remove-xaapplicationaccount -Accounts $account
			}
			## publish application to AD group
			$newapp | add-xaapplicationaccount -Accounts $publishTo
			## rename new application
			$newapp | rename-xaapplication -newdisplayname $newname						
		}}}

Download the script from GitHub.

Just set the variables with what is appropriate for your environment and run the script.
The $appFolder variable is the root path of your applications (default is “Applications”)
$testfolderpath is the full path of the folder where the applications published to individual servers will be published (no trailing slash)
Set $sourceAppPath one level higher than the path of the folder containing the applications you wish to copy. The script will recurse down into child folders from this path, but will not copy applications that reside within this path.
$publishTo is a string containing the AD users or groups the newly published applications should be published to
$targetWorkerGroup is the name of the worker group containing the servers the applications will be published to
$clientFolder is the root Program Neighborhood/Client folder that the individual server folders will be created under

The once-daunting and time-consuming task of copying and modifying applications for each server can now be done automatically in a matter of minutes!

Update: It’s even easier to undo the changes made by the above script if you decide you want to scrap the newly published apps and folders and start over. Just use the following script:

$testfolderpath =  "Applications/Test/Single Server Test"
$WhatIfPreference = $false

# Main Program
(get-xaapplication -folderpath (Get-XAFolder -folderpath $testfolderpath)) | remove-xaapplication
(get-xafolder -folderpath $testfolderpath) | remove-xafolder

How To Create XenApp6 Published Applications for Test In Just Seconds Using PowerShell

Prerequisite: XenApp6 PowerShell SDK

Let’s say you want to copy all currently published applications into a folder named “Test” in the console tree, while simultaneously modifying the new published apps with different permissions and client folder settings. Here’s how:

First, create the “Test” folder by hand (you can use New-XAFolder -FolderPath Applications\Test if you are so inclined), then use the following command to copy the applications into it:

get-XAApplication | Copy-XAApplication  -folderpath Applications\Test

Second, modify the published application properties to set the client folder (what folder the applications show up under in Program Neighborhood or Web Interface), and the permissions in one fell swoop. We’ll call the client folder “Test” and publish to the groups “domain1\citrix admins” and “domain2\Test Users”:

Get-XAApplication -folderpath Applications\Test | set-xaapplication -clientfolder Test -accounts "domain1\citrix admins","domain2\Test Users"

That’s it! Now doesn’t that beat right-click -> “Duplicate Application”?

Citrix XenApp6 0x80060016 Error In PowerShell

I ran into a little snag when executing some XenApp PowerShell commands. Certain commands like Get-XAFarm and Get-XAAdministrator would always give an “0x80060016” error. Here is an example and the fix:

PS C:\Windows\system32> Get-XAFarm
Get-XAFarm : Error reading the current administrator data (0x80060016)
At line:1 char:11
+ Get-XAFarm <<<<
+ CategoryInfo : InvalidResult: (:) [Get-XAFarm], CitrixException
+ FullyQualifiedErrorId : GetCitrixAdminType,Citrix.XenApp.Commands.GetFarmCmdlet

Typically this error code in Citrix indicates a problem with IMA. But in this case it was even simpler than that: IMA couldn’t resolve the hostname of the database server hosting the data store. Make sure that the correct DNS suffixes are being applied so IMA can find the server, and if that fails, just add it to the hosts file and try again.

How To Save Associated HABTM Models in CakePHP

A common problem programmers have with CakePHP is saving related models with a many-to-many relationship, something Cake calls “hasAndBelongsToMany” or HABTM. One of the things that makes saving HABTM models so challenging is that when you go to save your models, Cake will act like everything saved fine when in fact only one of the models saved, or in some cases, none of them saved.

To illustrate how to overcome this common problem, let’s say you’ve just read Dr. Seuss’ Butter Battle Book. In the book, the Yooks eat their bread butter side up, and the Zooks eat it butter side down, and they’re always fighting about it. You decide to write a social network app to try to bring these two groups together.

Of course, as with any respectable social network, you’ll need a way to allow Zooks and Yooks to become friends. Since a Zook can have many Yook friends, and a Yook can have many Zook friends, This will require three models in a HABTM relatonship: Yooks, Zooks, and Yooks_Zooks to join the two models together (CakePHP orders the model names alphabetically, so Zooks_Yooks would not work).

Since Zooks and Yooks still don’t see eye-to-eye on the whole butter side orientation thing, you decide it will be necessary to write separate controllers for Zooks and Yooks. You start by creating the controller for Yooks.

In your first action, you want a Yook to be able to invite a Zook to the new social networking site. This will require creating a new account for the Zook on the fly, as well as linking it to the Yook’s account so the site knows they are friends.

$this->Zook->create();
$this->Zook->ZooksYook->create();
$this->data['ZooksYook']['Yook_id'] = $this->Auth->User('id');
if ($this->Zook->save($this->data)) {	
  $this->data['ZooksYook']['Zook_id'] = $this->Zook->id;
  $this->Zook->ZooksYook->save($this->data);
   ...
}

Looking at it line-by-line, the first thing we do is initialize a new Zook. Next, we have to initialize a new ZooksYook, which as you recall is the model linking Zooks and Yooks. In our ZooksYook model, we only have three fields (you could have more, but for simplicity let’s stick with three): id, zook_id, and yook_id. Since this controller is for the Yook, his user id will go into the yook_id field of the new row we want Cake to create for us in the database. That is easy enough.

But before we can put anything in the zook_id field, we have to know what the new Zook’s id is. He doesn’t exist in the database yet and has no id, so we have to save the Zook. Upon a successful save, we then retrieve the Zook’s id and set the zook_id field in the ZooksYook model equal to it. Finally, we save the ZooksYook model.

The above is a unique use case for HABTM, and it is certainly not always necessary to do it the way I just showed you. Other ways of accomplishing the same thing are out there, but they didn’t neatly fit into my existing code so I experimented until I got something different that worked. Enjoy!