Use Copy-Property to Make it Easier to Write, Read,and Review Scripts

Use Copy-Property to Make it Easier to Write, Read,and Review Scripts

  • Comments 5

<Updated script on 1/1/2007 to reflect fixes suggested by James Manning> 

Dennis Verwiej is doing some great PowerShell work over at his blog Just PowerShell It at http://dverweij.spaces.live.com/ .   Recently he posted a blog entry Import Citrix Published Application in which he Imports a CSV file (previously exported) and then calls a few APIs to recreate the published application.  If you at the code (in the middle of this blog entry), it the vast bulk of it is a series of lines setting properties before calling the API.  I find that this is quite common - I end up writing tons of mind-numbing housekeeping code.  Because there is so much of it, it is often hard to tell whether it is right or not.  For instance, did you get all the properties set or did you forget a couple?

Why not leverage the special capabilities of PowerShell to make this problem go away?  Below is a routine that I use for such situations.  From its name (Copy-Property), you can guess that it copies properties from one object to another based upon the names of the properties. 

You have to deal with the situation of what to do if you try to set a property that doesn't exist.  You can either "just continue" or you can "add that property to the object" (remember that  in PowerShell, you can add members [properties, methods, propertysets, etc] to any object type or instance).  In my routine, I add the elements which in most cases is the thing you want to do or at worst - is benign.

function Copy-Property ($From, $To, $PropertyName ="*")
{
   foreach ($p in Get-Member -In $From -MemberType Property -Name $propertyName)
   {  trap {
         Add-Member -In $To -MemberType NoteProperty -Name $p.Name -Value $From.$($p.Name) -Force
         continue
      }
      $To.$($P.Name) = $From.$($P.Name)
   }
}

Leveraging this routine allows you to transform this code:

# First the known steps to connect to the mfcom and initialize the object 
$c_farm = new-object -com metaframecom.metaframefarm
$c_farm.initialize(1)
# Then we import the csv file and for each we do..
import-csv c:\citrix\Citrix_apps.csv | foreach{  
   $c_app=$c_farm.addapplication($_.apptype)
   $c_app.Appname = $_.appname
   $c_app.description = $_.description
   $w_app = $c_app.winappobject
   $w_app.defaultencryption = $_.defaultencryption
   $w_app.defaultinitprog = $_.defaultinitprog
   $w_app.defaultworkdir = $_.defaultworkdir
   $w_app.browsername = $_.browsername
   $w_app.enableapp = $_.enableapp
   $w_app.hidefrompnenum = $_.hidefrompnenum
   $w_app.hidefrombrowserenum = $_.hidefrombrowserenum
   $w_app.defaultwindowcolor = $_.defaultwindowcolor
   $w_app.defaultwindowtype = $_.defaultwindowtype
   $w_app.defaultsoundtype = $_.defaultsoundtype
   $w_app.desktopintegrate = $_.desktopintegrate
   $w_app.mfattributes = $_.mfattributes
   $w_app.publishingflags = $_.publishingflags
   $w_app.PNAttributes = $_.PNAttributes
   $w_app.AllowMultiInstancePerUser = $_.AllowMultiInstancePerUser
   $w_app.StartMenuFolder = $_.StartMenuFolder
   $w_app.PlaceUnderProgramsFolder = $_.PlaceUnderProgramsFolder
   $w_app.WaitOnPrinterCreation = $_.WaitOnPrinterCreation
   $w_app.defaultwindowwidth = $_.defaultwindowwidth
   $w_app.defaultwindowheight = $_.defaultwindowheight
   $w_app.defaultwindowscale = $_.defaultwindowscale
   $w_app.parentfolderDN = $_.parentfolderDN
   $w_app.pnfolder = $_.pnfolder
   $c_app.savedata()
}

into this:

# First the known steps to connect to the mfcom and initialize the object 
$c_farm = new-object -com metaframecom.metaframefarm
$c_farm.initialize(1)
# Then we import the csv file and for each we do..
import-csv c:\citrix\Citrix_apps.csv | foreach{
   $c_app=$c_farm.addapplication($_.apptype)
   $c_app.Appname = $_.appname
   $c_app.description = $_.description
   $w_app = $c_app.winappobject
   Copy-Property -From $_ -To $w_app 
   $c_app.savedata()
}

See how much easier that is to Write, Read and Review? 

Enjoy! 

Jeffrey Snover [MSFT]
Windows PowerShell/MMC Architect
Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell
Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

NOTE - I didn't run this so I can't verify that added the extra properties to $W_APP is OK but I'm pretty sure it is.  If it isn't, just tweak the TRAP to continue without doing the ADD-MEMBER.

 

Postscript.  10,000 thanks to James Manning who spotted (and diplomatically pointed out) a number of flaws in my Copy-Property script.  I've updated the script to reflect his corrections.  This reinforces the point about the benefits of sharing that I made in The Admin Development Model and Send-Signature entry:

By sharing, others get the benefit of seeing how you solve problems and it gives others the opportunity to point out how you can improve your scripting or to make you aware of things that you didn't know that you could do. 

Thanks James!

Leave a Comment
  • Please add 7 and 7 and type the answer here:
  • Post
  • I was kinda surprised that this would work:

    $To.$($P.Name) = $From.$(P.Name)

    I would have figured that the $(P.Name) would need to be $($P.Name) like the first part, but apparently not - could you explain why?

    Also, I like that the Add-Member call uses -In and would think you'd want to do so with the Get-Member call as well.  As-is, I'd imagine the function breaks when $From is an enumerable type since $From | Get-Member will fetch the properties of the items inside the collection instead of the properties of the collection (or collection-ish type of object) itself, so the subsequent $From.$(P.Name) would break... right?

    One other note is that the add-member -force approach has the effect of existing properties with getters but no setters (another reason the trap may be getting hit, in addition to the expected property-does-not-yet-exist) will get blown away and replaced with noteproperty's of the same name.  

  • > I would have figured that the $(P.Name) would need to be $($P.Name) like the first part, but apparently not - could you explain why?

    That is a bug. Thanks for catching it.  The reason why it worked was that it was throwing an exception and the addition was being done by the Add-Member in the trap handler.

    > As-is, I'd imagine the function breaks when $From is an enumerable type since $From | Get-Member will fetch the properties of the items inside the collection instead of the properties of the collection (or collection-ish type of object) itself, so the subsequent $From.$(P.Name) would break... right?

    Right again!  I'll fix the post.

    > One other note is that the add-member -force approach has the effect of existing properties with getters but no setters .. will get blown away and replaced with noteproperty's of the same name.  

    Correct. Ditto if the set fails because of security reasons.

    Jeffrey Snover [MSFT]

    Windows PowerShell/MMC Architect

    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • I've wondered if subsequent releases would include a clone-object cmdlet to address this kind of scenario.

  • > I've wondered if subsequent releases would include a clone-object cmdlet to address this kind of scenario

    Rob - say more about what you are thinking.  Maybe write a sample of what you'd like to be able to do.

    Jeffrey Snover [MSFT]

    Windows PowerShell/Aspen Architect

    Visit the Windows PowerShell Team blog at:    http://blogs.msdn.com/PowerShell

    Visit the Windows PowerShell ScriptCenter at:  http://www.microsoft.com/technet/scriptcenter/hubs/msh.mspx

  • This does not allow you to copy a copied object, because Get-Member filters on Property. The same problem occurs with objects you defined yourself using Add-Member.

    Fix: foreach ($p in Get-Member -In $From -MemberType Property,NoteProperty -Name $propertyName)

Page 1 of 1 (5 items)