So in the never ending GPM project, a new requirement came about recently.  It was a report to show the current state of the GPOs within Quest Group Policy Manager before any of the other work is done.  I wound up dubbing the report a ‘pre-flight’ report since we have one already for VAS and its a name that Ross J came up with a few years at another company he and I worked at. 

The first pre-flight report, which just listed every GPO and some info, was done in about 10-15 minutes during the meeting with the client.  However, they also pointed out that there may be many, many GPOs, and they would like to pare the list down to a specific subset of GPOs.  Of course, that proved a little more challenging but I managed to do it  in short order.  So now the script takes in the file path of a CSV and assumes GPOName is the column name with all the GPOs listed.

So, without further ado, here is the preliminary script:

########################################################################################################################################
#
# In an ideal world, this would be a cmdlet called:
#    Preflight-QGPO GPOName [-GPMServer] [-GPMPort] [-GPOListCSV]
#
########################################################################################################################################

Set-ExecutionPolicy Unrestricted;

########################################################################################################################################
# the next section is all hard coded variables which need to be set to script parameters
########################################################################################################################################
# set this param to $true to start taking in command line arguments
$useParams = $true;

if ($useParams)
{
 # Which GPM Server to export from
 $GPMHostname = $args[0];
 
 # Which GPM Server port to use
 $GPMPort = $args[1];
 
 # the location of a CSV file with a list of specified GPOs
 $GPOListCSV = $args[2];
}
else
{
 # Which GPM Server to export from
 $GPMHostname = "localhost";
 
 # Which GPM Server port to use
 $GPMPort = 40200;
 
 # the location of a CSV file with a list of specified GPOs
 $GPOListCSV = "";
# $GPOListCSV = "C:\GPMScripts\GPMPreflight\PreflightGPOs.csv";
}

function OutputGPOSettings ($p_currentGPO)
{
  $gpoName = $p_currentGPO.Name;
  $gpoVersion = $currentGPO.Version;
  $gpoStatus = $currentGPO.Status;
  $gpoLive = $currentGPO.HasLive;
  $gpoTrustee = $currentGPO.Trustee;
  $gpoApprovalRequired = $currentGPO.ApprovalsRequired;
  $gpoApprovalReceived = $currentGPO.ApprovalsReceived;
  
  Write-Output "GPO $iCounter : '$gpoName' - the most current version is $gpoVersion ";
  Write-Output "   The current status is $gpoStatus "
 # not quite sure how useful this is - it is basically live/not live indicator - but is not for the most current version
 # Write-Output "   Is a version of this GPO live ? $gpoLive ";
 
  if (($gpoStatus -eq $statusCheckedOut) -or ($gpoStatus -eq $statusPending) -or ($gpoStatus -eq $statusPendingDeployment ))
  {
   Write-Output "   The current user is: $gpoTrustee ";
   Write-Output "   The approval count required? $gpoApprovalRequired ";
   Write-Output "   The approval count received? $gpoApprovalReceived ";
  }
    
  Write-Output "";
}
#############################################################################################################
# include the GPM stuff - redirected to $null to avoid output to logfile
& 'C:\Program Files\Quest Software\Quest Group Policy Manager\QGPMInit.ps1' -computerName $GPMHostname -portNumber $GPMPort > $null

#valid GPO status for import operation are Available or Check-out
$statusAvailable = [Quest.Avalanche.Enums.StatusType]::Available;
$statusCheckedOut = [Quest.Avalanche.Enums.StatusType]::CheckedOut;
$statusDeleted = [Quest.Avalanche.Enums.StatusType]::Deleted;
$statusError = [Quest.Avalanche.Enums.StatusType]::Error ;
$statusErrorNoWorkingCopy = [Quest.Avalanche.Enums.StatusType]::ErrorNoWorkingCopy ;
$statusPending = [Quest.Avalanche.Enums.StatusType]::Pending ;
$statusPendingDeployment = [Quest.Avalanche.Enums.StatusType]::PendingDeployment ;
$statusUnregistered = [Quest.Avalanche.Enums.StatusType]::Unregistered ;

Write-Output "----------------------------";
Write-Output "Beginning Preflight Check of GPM Server '$GPMHostname' on port $GPMPort ";

$iCounter = 0;

if (($GPOListCSV -eq "") -or ($GPOListCSV -eq $null))
{
 # loop through all the objects in the data set and find the policy we want
 foreach($currentGPO in $VCManager.GetControlledObjects("GPO"))
 {
  $iCounter = $iCounter + 1;
  OutputGPOSettings $currentGPO;
 }
}
else
{
 # bring the file
 $GPOList = Import-Csv $GPOListCSV;

 # loop through all the objects in the data set and find the policy we want
 :GPOLoopCSV foreach($currentGPO in $VCManager.GetControlledObjects("GPO"))
 {
  foreach ($GPOName in $GPOList)
  {
   if ($currentGPO.Name -eq $GPOName.GPOName )
   {
    $iCounter = $iCounter + 1;
    OutputGPOSettings $currentGPO;
   }
  }
 }
}

if ($foundGPO -eq $false)
{
 Write-Output "No GPO named '$gpoName' was found.";
}
Write-Output "Preflight Completed";
Write-Output "----------------------------"; 

{ Comments on this entry are closed }

I just got an Amazon Kindle, and think its fantastic. However, I’m now torn on what to do with 1 particular book;  Infinite Jest by David Foster Wallace.  The reason is that its a massive book (1100 pages) and I am on page 20.  Should I re-purchase it on the Kindle? The Kindle seems ideal for it, yet I don’t want to spend another $10 on a book I already own.  If I don’t, I doubt I’ll actually get to finish it as I have very little time to read outside riding the train, and the book is just too big to carry around.  Plus, I have 12+ hours of train time coming up this week . . .

{ Comments on this entry are closed }

(This post was written a while back and has been held up in drafts)

Yesterday (29-5-2009), I was a customer that had 400 users, yet 1,300 active accounts in AD.  And these were not stray/orphaned accounts, but those used for actual services.  One thing they’re not aware of is a new type of object in AD called that Managed Service Accounts.  Regardless, even having that many accounts for so few users is alarming.

They definitely need a better management strategy for managing all those accounts because there’s simply no way to properly keep up with this accounts.  We’ll be working with them in the coming months to help them deploy out Quest ActiveRoles Server to start getting some of this under control and I’m sure it will be a sea change for them once they get a handle on that tool.  I’ll try and keep this site posted on what they do.

{ Comments on this entry are closed }

I had a pre-release (dare I say “beta”) of this video, and some have shared the links already, but I’ve completed the 10 minute epic that is called “Quest For Password Management.”  The video is rendering and uploading now, and should be up by 11:30 AM GMT.  Its actually a video showing 3 of Quest’s Identity Management products; Quest Password Manager, Quest Defender and Quest Webthority.  Note that Webthority is now part of Defender, and we’re sure to change the name in the future.

Anyway, the recording can be found here:

https://www.idmwizard.com/quest/QuestForPasswordManagement/QuestForPasswordManagement.html

Techie sidenote: The thing that took the longest? Setting up a CA (Certificate Authority).  Now, you’re probably going to ask yourself, “why on earth do I need a CA to do password management?”  And the answer is “you don’t.”  However, if you wish to perform an AD password change, its best to do it over LDAP-S (port 636) rather than plain LDAP (port 389).  And in trying to set this up in Webthority, I swore I found a bug (yes, even Quest products have bugs from time to time).  But it turns out that I just wasn’t configuring the CA properly, and it took our fine support people to point out that I simply didn’t know what I was doing.  In fact, I’m told there will be a support KB article on the topic shortly.  I’ll keep you posted.

{ Comments on this entry are closed }

Back when I was a dev manager, a site appeared that really intrigued me: Rent A Coder.  I’ve been keeping tabs on for a while now, and finally joined up.  I’m actually surprised its not taken on more prominence, but I do see quite a bit of activity on it.  The most interesting stat is that they have plateaued to having on 5-6% new buyers, and 94%+ of the work is done for repeat customers.

Quick primer on how RAC works: Buyers post up requirements for a project, and developers bid on them.  Once a bid is accepted, the payment for the project is put into an escrow account, and the developer is paid from the account once the project is delivered and tested.  RAC acts as the third party to both buyers and sellers, taking a 15% fee for their services.  Its kind of like ebay for dev projects.

Anyway, I really think this sort of thing is not used enough.  Just the work I do, day to day, mirrors this sort of task-oriented approach.  And if you have a solid enough set of requirements, you should be able to farm out discrete ‘units’ of work and just piece them together.

(note: funny that this post languished in my Drafts for over a month – I really need to check this more often)

{ Comments on this entry are closed }

Still plodding through the GPO scripting through Quest Group Policy Manager given all the other things I have going on but I cranked this out last week quickly to meet a quick checklist item. As the comments in the code say, “In an ideal world, this would be a cmdlet called Delete-QGPO. ”  The only change you need to make is to change the $useParams value to $true to start using it like a cmdlet and feed it the params specified.  As with all things PowerShell, the intent is to call this on 1 object (a GPO) and if you want to use it on a set of objects, get that list, and pipe it in, like so:

import-csv deletelist.csv | % {& Delete-QGPO.ps1 $_.GPOName $_.GPMServer $_.ApprovalRequired}

That is it.  Now for all the code . . .

#############################################################################################################
#
# In an ideal world, this would be a cmdlet called:
#    Delete-QGPO GPOName [-GPMServer] [-GPMPort] [-ApprovalRequired]
#
#############################################################################################################
Set-ExecutionPolicy Unrestricted;
#############################################################################################################
# the next section is all hard coded variables which need to be set to script parameters
#############################################################################################################
$useParams = $false;
if ($useParams)
{ 
 # define the GPO name, which is what people will probably know it as – this can be an argument to a script later
 $gpoName = $args[0];
 # Which GPM Server to export from
 $GPMHostname = $args[1];
 $GPMPort = $args[2];
 
 $ApprovalRequired = $args[3];
}
else
{
 # Which GPM Server to export from
 $GPMHostname = "localhost";
 
 $GPMPort = 40200;
 
 # define the GPO name, which is what people will probably know it as – this can be an argument to a script later
 $gpoName = "Test Policy";
 
 $ApprovalRequired = "no";
}
#############################################################################################################
& 'C:\Program Files\Quest Software\Quest Group Policy Manager\QGPMInit.ps1' -computerName $GPMHostname
$foundGPO = $false ;
# loop through all the objects in the data set and find the policy we want
foreach($currentGPO in $VCManager.GetControlledObjects("GPO") |
      Where-Object {$_.Name -eq $gpoName})
{
 $foundGPO = $true;
 
 # count the number of deployed versions
 $counter1 = 0;
 
 $DeleteGPO = $false;
 
 # check out the GPO so we can edit it
 # you can discard the contents returned since we want a previous version
 $VCManager.Delete($currentGPO.VCId, "Deleting GPO - bye bye");
 Write-Output "Requesting approval to delete GPO $gpoName";
 if ($ApprovalRequired.ToUpper().Substring(0,1) -eq "N")
 {
  $VCManager.Approve($currentGPO.VCId, "Requesting Approval");
  $VCManager.Deploy($currentGPO.VCId, "Deploying (actually deleting) GPO")
  Write-Output "GPO $gpoName deleted";
 }
}
if ($foundGPO -eq $false)
{
 Write-Output "GPO $gpoName not found"
}

{ Comments on this entry are closed }

I also put together a doc this week that was off the beaten path.  And what was different was not the content but the presentation.  It was a 4-5 page doc, but the guy reading it doesn’t tolerate emails that go ‘below the fold.’  So right after the short summary/intro was a link that said ‘Click here to go to the conclusion.’  So our 30 minute chat had him read the intro, conclusion, and then look at the details last.

{ Comments on this entry are closed }

My Quest GPM/PowerShell/PowerGUI grind continues – I’ve gotten the export working fully, and its pretty well designed.  I’m determined to make each script into a usable set of cmdlets.  When I finish the entire project, I’ll post the final code here.  Right now, everything revolves around Quest’s GPM, but there is some pretty involved code which could be re-purposed for other tasks (perhaps even straight AD GPO manipulation).

But what I haven’t really considered until early this week are the actual requirements.  And then, that same consideration needed to be made by the PM and the Dev guys helping me.  And that’s because the requirements were not being observed in the original pass of what was developed. Ian D (my “customer” inside Quest) actually did something I hadn’t done in quite a while – he made me review the dev requirements before we did anything else on Tuesday.

And those requirements, simply put, was that the user getting the set of imported files should be able to run a single script/executable and all the ‘magic’ happens unattended.  No intervention should be needed, and if there’s a problem, the user is not going to be able to help.  So just dump a log file, and let the user send the log back to ‘the mother ship.’  Even if the user is able to help, he will have little to no rights in AD.

That one rule (that the user will have NO rights in AD) caused me to restructure and rethink some things on Tuesday.  I’m now 80% done, and hope to have something in a deliverable draft on Monday afternoon.  And I’ve taken on board what Ian did to get me focused on Tuesday – I wrote out the requirements and have them in front of me as I’m coding.  I forgot how easy it is to lose focus and ‘go dark’ if you don’t keep the goal in sight.

{ Comments on this entry are closed }

Lately, I’ve noticed an annoying trend by vendors.  They are starting to put their content onto USB sticks, instead of CDs.  Now, on the surface, this sounds really good.  There’s less waste, since a USB stick is reusable, whereas a CD is usually obsolete within a month or two.  However, vendors are taking a chunk of the stick, and permanently putting their content on.  So on a 512 MB stick, less than 100 MB is taken up by some demo, along with a bunch of PDFs, and a whole partition exists just for that content.  However, the 100 MB is permanent, and you cannot get it back by formatting the entire stick – I’ve tried. 

I’ve got a perfectly good 512 MB stick that has 40 MB of a partition from 2007.  There is nothing interesting on that partition now, and I can get much newer content off the vendor’s website, but it will remain there forever.  I can understand wanting to make sure the client gets the content, but after some time, with stale content, why not give the client the ability to get the entire USB stick back?  And I bet it would be cheaper to produce the sticks, too, instead of paying for the extra partitioning and autorun software.

{ Comments on this entry are closed }

So I am now getting further into the GPM script, and am writing a script that could ultimately become a cmdlet.  This one exports out a GPO and its associated links.  Next week, I’ll have the complementary import posted.  Everything is hard-coded, but you can see how the script would be parameterised.

———————-

#############################################################################################################
#
# In an ideal world, this would be a cmdlet called:
#    Export-QGPO GPOName [-FilePath] [-DomainName] [-GPMServer] [-GPMPort] [-IncludeLinks] [-PreviousVersion]
#
#############################################################################################################
Set-ExecutionPolicy Unrestricted;
#############################################################################################################
# the next section is all hard coded variables which need to be set to script parameters
#############################################################################################################
# define the GPO name, which is what people will probably know it as – this can be an argument to a script later
$gpoName = "VAS Policy";
# location where to put the exported GPOs
$backupPath = "C:GPMScriptsscratch";
# how far back to go - 1 is the last deployed version
# note: this is changed from previous version which got the live GPO - this is by design
$previousVersionCount = 1;
# Which GPM Server to export from
$GPMHostname = "localhost";
# specify whether links ought to be exported along with the GPO itself
$IncludeLinks = $true;
# the name of the current domain - should be pulled from GPM probably
$CurrentDomain = "quest.local";
#############################################################################################################
& 'C:Program FilesQuest SoftwareQuest Group Policy ManagerQGPMInit.ps1' -computerName $GPMHostname
$foundGPO = $false ;
# loop through all the objects in the data set and find the policy we want
foreach($currentGPO in $VCManager.GetControlledObjects("GPO") |
      Where-Object {$_.Name -eq $gpoName})
{
      $foundGPO = $true;
      
      # count the number of deployed versions
      $counter1 = 0;
      $exportSuccess = $false;
      # now start rolling through history - note: the array brought back by getHistory is unsorted
      # so we need to sort it, and find the first 'Deploy' version 
      foreach ($action in $VCManager.GetHistory($currentGPO.VCId) | Sort-Object -Descending Version)
      {
            # pull back only deployed objects, since we need to go 1 back
            # this should probably be deployed or registered GPOs -
            # someone else can put in the additional check
            if ($action.Type -eq "Deploy")
            {
                  $counter1 += 1;
                  
                  # 1 is really the last deployed version - which is what probably ought to be the default
                  if ($counter1 -eq $previousVersionCount)
                  {
       # Retrieve a backup from version control
                    $GPOBackup = $VCManager.GetBackup( $currentGPO.VCId, $action.BackupId);
     # if we got back something valid, start the export
     if( $null -ne $GPOBackup)
        {
      $fileName = $gpoName + ".zip";
      [System.IO.File]::WriteAllBytes( [System.IO.Path]::Combine( $backupPath, $fileName ), $GPOBackup.Bytes );
      # go into this section if you want to export links at the same time as the GPO
      if ($IncludeLinks)
      {
       # get a collection of all GPOLinks
       $currentGPOLinks = $VCManager.GetGpoLinks($CurrentDomain,$currentGPO.Id);
       
       [System.IO.StreamWriter] $LinkFile;
       
       $LinkFile = [System.IO.File]::CreateText([System.IO.Path]::Combine( $backupPath, $gpoName + " Links.xml"));
       foreach ($currentLink in $currentGPOLinks | Sort-Object -Descending LinkOrder )
       {
        $LinkFile.WriteLine("<GPOLink>");
        $LinkFile.WriteLine("  <SOMPath>" + $currentLink.SOMPath + "</SOMPath>");
        $LinkFile.WriteLine("  <LinkOrder>" + $currentLink.LinkOrder + "</LinkOrder>");
        $LinkFile.WriteLine("  <Enabled>" + $currentLink.Enabled + "</Enabled>");
        $LinkFile.WriteLine("  <Enforced>" + $currentLink.Enforced + "</Enforced>");
        $LinkFile.WriteLine("</GPOLink>");
       }
       $LinkFile.Close();
       $LinkFile.Dispose();
      }
     }
     
     $exportSuccess = $true;
                  }
            }
   # should probably break out of the loop here
      }
}

{ Comments on this entry are closed }