Monday, May 13, 2013

Powershell Scripting Games event 2

Voting has started for the second assignment of the 2013 Powershell Scripting Games. This second task was very tough for me because I was on holiday at the seaside and had no fixed Internet connection and no Windows 2000 nor Windows 2003 test machine to test my script with. Anyhow I managed to answer the assignment by using MSDN and checking there which WMI classes I could query depending on the operatin system version.

But let's go back a bit. Here's the full task assignment:

"Dr. Scripto finally has the budget to buy a few new virtualization host servers, but he needs to make some room in the data center to accommodate them. He thinks it makes sense to get rid of his lowest-powered old servers first… but he needs to figure out which ones those are.

This is just the first wave, too – there’s more budget on the horizon so it’s possible he’ll need to run this little report a few times. Better make a reusable tool.

All of the virtualization hosts run Windows Server, but some of them don’t have Windows PowerShell installed, and they’re all running different OS versions.The oldest OS version is Windows 2000 Server (he knows, and he’s embarrassed but he’s just been so darn busy). The good news is that they all belong to the same domain, and that you can rely on having a Domain Admin account to work with.

The good Doctor has asked you to write a PowerShell tool that can show him each server’s name, installed version of Windows, amount of installed physical memory, and number of installed processors. For processors, he’ll be happy getting a count of cores, or sockets, or even both – whatever you can reliably provide across all these different versions of Windows. He has a few text files with computer names – he’d like to pipe the computer names, as strings, to you tool, and have your tool query those computers."

When I saw the scenario I was I little scared by the fact of having to handle all the possible operating systems since Windows 2000. I highlighted the critical points that need to be addressed by the script. I for sure came up with a function in roder to answer to the need of a reusable tool.

Here's the script I wrote:

  1. #Requires -Version 3.0  
  2.    
  3. Function Get-Inventory {  
  4.    
  5. <#  
  6.  .SYNOPSIS  
  7.    Collect important information about a system's hardware in order to decide which ones are too low-powered. 
  8.   
  9.  .DESCRIPTION 
  10.    This function will collect the following information about the target computer: 
  11.         - computer name,  
  12.         - operating system version, 
  13.         - the amount of physical memory, 
  14.         - number of processors,  
  15.         - number of cores, 
  16.         - number of sockets 
  17.    This function has the three classic Begin, Process and End block. 
  18.      - In the Begin block I prepare the ScriptBlock which contains the WMI queries and which is called in the process block. 
  19.      - In the process block I perform Job setup and Jobs throttling. 
  20.      - In the End block I return the results in the form of a object or of a GridView. 
  21.   
  22.   .PARAMETER ComputerName 
  23.     Gets the services running on the specified computer. The default is the local computer. 
  24.   
  25.     Type the NetBIOS name, an IP address, or a fully qualified domain name of a remote computer. 
  26.     To specify the local computer, type the computer name, a dot (.), or "localhost". 
  27.   
  28.     This parameter does not rely on Windows PowerShell remoting. 
  29.     You can use the ComputerName parameter even if your computer is not configured to run remote commands. 
  30.   
  31.   .PARAMETER Credential 
  32.     Specifies a user account that has permission to perform this action. 
  33.   
  34.     This parameter allows you to supply either a PSCredential object or you can specify the domain\username and it will open up 
  35.     the credential window to type in a password. 
  36.   
  37.   .PARAMETER Grid 
  38.     Use this switch to visualize inventory data in the new GridView format for better filtering. 
  39.   
  40.  .EXAMPLE 
  41.     To report on localhost: 
  42.   
  43.     Get-Inventory 
  44.   
  45.  .EXAMPLE 
  46.     To report on a list of servers from a text file: 
  47.   
  48.     Get-Inventory -ComputerName (Get-Content c:\servers.txt) 
  49.   
  50.  .EXAMPLE 
  51.     To report on all available servers in a domain, and see the results in a grid, use the following syntax: 
  52.   
  53.     Get-Inventory -ComputerName ( ([adsisearcher]"objectCategory=computer").findall() |  ForEach-Object { $PSItem.properties.cn } ) -Grid 
  54.   
  55.  .EXAMPLE 
  56.     To report against a specif server: 
  57.   
  58.     Get-Inventory -ComputerName "alphaserver" 
  59.   
  60.  .NOTES 
  61.    Entry for 2013SG Advanced Event 2. To use, dot source script and run 'Get-Inventory'. 
  62.    Use 'get-help Get-Inventory -full' to see how to run complete help. 
  63.  #> 
  64.   
  65.  [CmdletBinding()]   
  66.   
  67.     param( 
  68.   
  69.         [Parameter(Mandatory=$false, ValueFromPipeline=$True, ValueFromPipelineByPropertyName=$true)] 
  70.         # Binding by default to localhost. 
  71.         [Alias('CN','__Server')] 
  72.         [String[]]$ComputerName = $env:COMPUTERNAME, 
  73.   
  74.         [Parameter(Mandatory=$false)] 
  75.         [System.Management.Automation.PSCredential] 
  76.         [System.Management.Automation.Credential()]$Credential = [System.Management.Automation.PSCredential]::Empty, 
  77.   
  78.         [Parameter(Mandatory=$false)] 
  79.         [switch]$Grid = $false 
  80.   
  81.       ) # End parameter definition. 
  82.   
  83.   Begin { 
  84.   
  85.       Write-Verbose "Started at $(get-date)." 
  86.       # Setting up an array to keep al the objects returned from this function. 
  87.       $AllComputerData = @() 
  88.       # Setting up an array to store all the remote jobs. 
  89.       $Jobs = @() 
  90.       # Number of max computers to query at the same time. This parameter throttle WMI queries in order not to overload the Inventory server. 
  91.       $MaxConcurrentJobs = 10 
  92.   
  93.       # Checking if current is has administrator token for WMI queries. 
  94.       Write-Verbose "Checking for admin rights." 
  95.       $CurrentUser = [Security.Principal.WindowsIdentity]::GetCurrent()             
  96.       $TestAdmin = (New-Object Security.Principal.WindowsPrincipal $CurrentUser).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)             
  97.       if (!$TestAdmin) 
  98.         {             
  99.         Throw "This script must be run with elevated privileges. Try tu use -Credential parameter and suplly administrative credentials. Exiting..." 
  100.         } 
  101.       else 
  102.         { 
  103.         Write-Verbose "Admin rights OK. Continuing." 
  104.         } 
  105.   
  106.       # Scriptblock. Here is where everything happens. This scriptblock is called inside the Process block for each server to query. 
  107.       $Sb = { 
  108.             # The parameter $Computer is passed by Start-Job as an argument for consistency. I could have used args[0] too. 
  109.             param($Computer) 
  110.               # Checking if ping works.. 
  111.               if(!(Test-Connection $Computer -Quiet -ea 0 -Count 2)) 
  112.                 { 
  113.                 # Ping failed!!! 
  114.                 # Creating a dummy object for servers not pinging. I added the Ordered key to keep columns in the order I want. 
  115.                 $Result = New-Object –TypeName PSObject –Property ([ordered]@{ 
  116.                   'Computer Name'= $computer 
  117.                   'State' = "Not pinging" 
  118.                   'Operating System'= "N/A" 
  119.                   'Physical Memory'= "N/A" 
  120.                   'Number Of Processors'= "N/A" 
  121.                   'Number of Cores'= "N/A" 
  122.                   'Number of Sockets'= "N/A" 
  123.                   }) 
  124.                 } # End if. 
  125.               else 
  126.                 { 
  127.                 # Server pings. Checking WMI in a Try Catch block. 
  128.                 try 
  129.                   { 
  130.                   #Adding splatting to keep the line for WMI queries not too long 
  131.                   $Parms = @{ 
  132.                     "Computername" = $Computer; 
  133.                     "ErrorAction" = "Stop"; 
  134.                     "ErrorVariable" = "MyErr"; 
  135.                     "Impersonation" = "impersonate" 
  136.                     } 
  137.                   # Retrieving the three needed WMI instances: Win32_OperatingSystem, Win32_ComputerSystem and Win32_Processor. 
  138.                   $OperatingSystem = Get-WmiObject Win32_OperatingSystem -property Caption @Parms 
  139.                   $ComputerSystem = Get-WmiObject Win32_ComputerSystem -property Name,TotalPhysicalMemory,NumberOfProcessors @Parms 
  140.                   $Processor = Get-WmiObject Win32_Processor -Property SocketDesignation,Numberofcores @Parms 
  141.   
  142.                   # Retrieving information about cores and sockets 
  143.                   $NumberOfCores = 0 
  144.                   $NumberOfSockets = 0 
  145.                   [string]$String = $null 
  146.                   foreach ($Proc in $Processor) 
  147.                     { 
  148.                     # Checking if the NumberOfcores property is present in Win32_Processor. If it is not the case, then it's a Windows 2003 or older.  
  149.                     if ($Proc.numberofcores -eq $null)  
  150.                       {  
  151.                       # Checking if SocketDesignation property is available for each instance of Win32_Processor.  
  152.                       # If it is present I increment the $NumberOfSockets counter.  
  153.                       If (-not $String.contains($Proc.SocketDesignation))  
  154.                         {  
  155.                         $String = $String + $Proc.SocketDesignation  
  156.                         $NumberOfSockets++  
  157.                         }  
  158.                       # In Windows 2003 and older there is one instance of Win32_Processor for each core.  
  159.                       # So I am incrementing this counter for each instance.  
  160.                       $NumberOfCores++  
  161.                       }  
  162.                     else  
  163.                       {  
  164.                       # Since Windows Server 2008 and Windows Vista:  
  165.                       #   - for the Number of Cores,  there is a NumberOfCores property. Easy.  
  166.                       #   - for the Nymber of Sockets, each instance of Win32_Processor is a socket. Incrementing this counter for each instance.  
  167.                       $NumberOfCores = $NumberOfCores + $Proc.numberofcores  
  168.                       $NumberOfSockets++  
  169.                       }  
  170.                     } # End of foreach loop used to retrieve information on sockets and cores.  
  171.    
  172.                   # Retrieving Physical Memory  
  173.                   $PhysicalMemory = $ComputerSystem.TotalPhysicalMemory  
  174.                   # Converting installed RAM to the most appropriate unit. it's a bit long but it makes results easy to read for humans.  
  175.                   Switch ($PhysicalMemory)  
  176.                     {  
  177.                     {($PhysicalMemory -gt 1024) -and ($PhysicalMemory -le 1048576)}  
  178.                       {  
  179.                       # Better to use KB.  
  180.                       $PhysicalMemory = $PhysicalMemory/1Kb  
  181.                       $PhysicalMemory = [math]::Round($PhysicalMemory, 2)  
  182.                       $PhysicalMemory = [string]$PhysicalMemory + ' KB' 
  183.                       Break 
  184.                       } 
  185.                     {($PhysicalMemory -gt 1048576) -and ($PhysicalMemory -le 1073741824)} 
  186.                       { 
  187.                       # Better to use MB. 
  188.                       $PhysicalMemory = $PhysicalMemory/1Mb 
  189.                       $PhysicalMemory = [math]::Round($PhysicalMemory, 2) 
  190.                       $PhysicalMemory = [string]$PhysicalMemory + ' MB' 
  191.                       Break 
  192.                       } 
  193.                     {($PhysicalMemory -gt 1073741824)} 
  194.                       { 
  195.                       # Alot of memory here. Better to use GB. 
  196.                       $PhysicalMemory = $PhysicalMemory/1Gb 
  197.                       $PhysicalMemory = [math]::Round($PhysicalMemory, 2)  
  198.                       $PhysicalMemory = [string]$PhysicalMemory + ' GB' 
  199.                       } 
  200.                     default 
  201.                       { 
  202.                       # Bytes maybe. 
  203.                       $PhysicalMemory = [math]::Round($PhysicalMemory, 2) 
  204.                       $PhysicalMemory = [string]$PhysicalMemory + ' B' 
  205.                       } 
  206.                     } # End Switch. 
  207.   
  208.                   # Preparing a PSObject and filling it with hardware and operating system information. Keeping columns in order with Ordered key. 
  209.                   $Result = New-Object –TypeName PSObject –Property ([ordered]@{ 
  210.                     'Computer Name'= $ComputerSystem.Name 
  211.                     'State' = "Ping and WMI OK" 
  212.                     'Operating System'= $OperatingSystem.Caption 
  213.                     'Physical Memory'= $PhysicalMemory 
  214.                     'Number Of Processors'= $ComputerSystem.NumberOfProcessors 
  215.                     'Number of Cores'=$NumberOfCores 
  216.                     'Number of Sockets'=$NumberOfSockets 
  217.                     }) 
  218.                   } # End Try, starting Catch. 
  219.                 Catch 
  220.                   { 
  221.                   # Cought WMI error. 
  222.                   "WMI Query for $computer failed." 
  223.                   "The error was: $MyErr" 
  224.                   # Preparing a dummy object for servers that did not answer WMI query. 
  225.                   $Result = New-Object –TypeName PSObject –Property ([ordered]@{ 
  226.                     'Computer Name'= $computer 
  227.                     'State' = "Ping OK but WMI Query failed" 
  228.                     'Operating System'= "N/A" 
  229.                     'Physical Memory'= "N/A" 
  230.                     'Number Of Processors'= "N/A" 
  231.                     'Number of Cores'= "N/A" 
  232.                     'Number of Sockets'= "N/A" 
  233.                     }) 
  234.                   } # End Catch block. 
  235.                  } # End if else    
  236.             # Returning the result for this server to the calling function. 
  237.             Return $Result 
  238.         } # End of Scriptblock 
  239.   
  240.   } # End Begin section 
  241.   
  242.   Process { 
  243.   
  244.     foreach ($Computer in $ComputerName) { 
  245.   
  246.       # Preparing a progress bar to be used if the number of servers to query is greater than 1. 
  247.       # The parameters for the progress bar are stored in a variable named $ProgressSplatting. 
  248.       if ($ComputerName.Count -gt 1) { 
  249.         $ProgressSplatting = @{ 
  250.           Activity = 'Inventorying:' 
  251.           Status = $Computer 
  252.           Id = 1 
  253.           PercentComplete = ([array]::IndexOf($ComputerName, $Computer))/$ComputerName.Count*100 
  254.         } 
  255.         Write-Progress @ProgressSplatting 
  256.       } 
  257.   
  258.       #Replacing dot and localhost with proper computername. 
  259.       switch ($computer) 
  260.         { 
  261.         "."         {$computer="$env:COMPUTERNAME"}             
  262.         "localhost" {$computer="$env:COMPUTERNAME"}             
  263.         } 
  264.   
  265.       Write-Verbose "Starting job for $Computer" 
  266.       # Adding a job for each server in the pipe. 
  267.       $Jobs += Start-Job -ArgumentList $Computer -ScriptBlock $sb  
  268.       $Running = @($Jobs | Where-Object {$PSItem.State -eq 'Running'}) 
  269.       # Throttling jobs 
  270.       while ($Running.Count -ge $MaxConcurrentJobs) 
  271.         { 
  272.         write-verbose "Waiting for all jobs to complete" 
  273.         $Finished = Wait-Job -Job $Jobs -Any 
  274.         $Running = @($Jobs | Where-Object {$PSItem.State -eq 'Running'})  
  275.         } # End while loop.  
  276.     } # End of foreach loop against each server.  
  277.     # Waiting for every job inside $Jobs array to finish.  
  278.     Wait-Job -Job $Jobs > $Null  
  279.     Write-Verbose "All jobs finished!"  
  280.    
  281.   } # End Process block.  
  282.    
  283.   End {   
  284.     # Receiving content of jobs and storing them in a $AllComputerData array.  
  285.     $Jobs | ForEach-Object { $AllComputerData  += $PSItem | Receive-Job }  
  286.     Write-Verbose "Finished at $(get-date)."  
  287.     if($Grid)  
  288.       {  
  289.       # If you selected the -Grid parameters I am going to show the results in a GridView.  
  290.       Write-Verbose "Displaying output in a GridView."  
  291.       $AllComputerData | Out-GridView  
  292.       }  
  293.     else  
  294.       {  
  295.       # Returning an object for post-processing.  
  296.       Write-Verbose "Returning the array that contains all the inventory results."  
  297.       Return $AllComputerData  
  298.       } # End If  
  299.    
  300.     } # End of End block  
  301.    
  302. # End of Function Get-Inventory  
  303.    
  304. Get-Inventory -ComputerName . -Verbose  
For the moment my script was very well received, since I am in the Top 10 as you can see here:
This is a huge result for me since I din't spend more than three hours coding and that I had no test machine apart from my laptop.

I tried to be as prolific as I could, so I added jobs, which is a must when you are querying a lot of remote hosts. I also added the possibility to show the result in a GridView, which is a new feature of Powershell.

To vote for me on this entry, visit the link below and click on the rightmost star (5 stars)!
Thanks for your support!

Powershell Scripting Games Event 1, lesson learned

The 2013 Powershell Scripting Games have started for real. I am competing this is for the firs time in the Advanced category. For the moment two tasks have been published. In the first one we had to develop a function aimed at moving old logfiles to a location off the system drive. Easy right? Well, not really. The judges (as well as the other competitors) are very nitty-picky and I have soon discovered that any small mistake can bring very low scores.

Even tough I paid careful attention to what I was doing, I overlooked a few things which I want to share here.

Here's the function I wrote for the assignment:
  1. #Requires -Version 3.0  
  2.   
  3. set strict-mode latest  
  4.   
  5. function Archive-Logs {  
  6. <#  
  7.  .SYNOPSIS  
  8.    Archives application logs older than a certain amount of days to a specific location.  
  9.   
  10.  .DESCRIPTION  
  11.    Moves all the application logs older than a certain amount of days to a specific location.  
  12.    The subfolder structure is maintained.  
  13.   
  14.  .PARAMETER SourcePath  
  15.    Specifies the current path of the folder containing the logs to be moved.  
  16.   
  17.  .PARAMETER DestinationPath  
  18.    Specifies the destination path where to recreate the subfolders structure and move the logs.  
  19.   
  20.  .PARAMETER Days  
  21.    Specifies the minimum age in days of the logs that have to be archived.  
  22.    The default is 90 days.  
  23.   
  24.  .PARAMETER Log  
  25.    Switch parameter to enable error logging.  
  26.    The default is False.  
  27.   
  28.  .PARAMETER FailedLogFile  
  29.    Specifies the path of the file where the errors are logged if the Log switch is set to True.  
  30.    The default is $env:temp\failed-log-move.log  
  31.   
  32.  .PARAMETER ViewErrorLog  
  33.    Opens the error log for viewing using the default associated program (i.e. Notepad).  
  34.    The default is False.  
  35.    
  36.  .EXAMPLE  
  37.    Archives logs older than 100 days from C:\Application\Log to \\nasserver\Archives  
  38.   
  39.    Archive-Logs C:\Application\Log \\nasserver\Archives 100  
  40.    
  41.  .EXAMPLE  
  42.    Archives logs older than 100 days from C:\Application\Log to \\nasserver\Archives. It also logs errors to $env:temp\failed-log-move.log and opens this log for reading.  
  43.   
  44.    Archive-Logs C:\Application\Log \\nasserver\Archives 100 -Log -ViewErrorLog  
  45.   
  46.  .EXAMPLE  
  47.    Archives logs older than 2 years from C:\Application\Log to \\nasserver\Archives and shows verbose output  
  48.      
  49.    Archive-Logs C:\Application\Log \\nasserver\Archives 730 -Verbose  
  50.    
  51.  .EXAMPLE  
  52.    Simulates archival of logs older than 3 months from C:\Application\Log to d:\backup  
  53.      
  54.    Archive-Logs C:\Application\Log d:\backup 90 -Whatif  
  55.   
  56.  .NOTES  
  57.    Entry for 2013SG Advanced Event 1. To use, dot source script and run 'Archive-Logs'.  
  58.    Use 'get-help Archive-Logs -full' to see how to run complete help.  
  59.      
  60.    This function leverages Powershell V3 cmdlets and parameters, such as OlderThan in Test-Path or $PSItem instead of $_.  
  61.    I am also taking advantage of Get-ChildItem -File and -Directory.  
  62.      
  63. #>  
  64.     
  65.   [CmdletBinding(SupportsShouldProcess=$True)]  
  66.     
  67.   param(  
  68.     [Parameter(Mandatory, Position=0)]  
  69.     [ValidateScript({Test-Path $_ -PathType Container})]  
  70.     [String]$SourcePath,  
  71.   
  72.     [Parameter(Mandatory, Position=1)]  
  73.     [ValidateScript({Test-Path $_ -PathType Container})]  
  74.     [String]$DestinationPath,  
  75.   
  76.     [Parameter(Mandatory=$False, Position=2)]  
  77.     [Int]$Days = 90,  
  78.   
  79.     [Parameter(Mandatory=$False, Position=3, ParameterSetName = 'Logging')]  
  80.     [Switch]$Log = $False,  
  81.   
  82.     [Parameter(Mandatory=$False, Position=4, ParameterSetName = 'Logging')]  
  83.     [ValidateScript(  
  84.       {  
  85.       Try {  
  86.         Add-Content -LiteralPath $_ -Value $null -ErrorAction Stop  
  87.         $true  
  88.         }  
  89.       Catch { $false }  
  90.      })]  
  91.     [String]$FailedLogFile=(Join-Path $env:Temp 'failed-log-move.log'),  
  92.   
  93.     [Parameter(Mandatory=$false, Position=5, ParameterSetName = 'Logging')]  
  94.     [switch]$ViewErrorLog = $false  
  95.     )  
  96.   
  97.     Write-Verbose ("Script started processing at {0:hh}:{0:mm}:{0:ss}" -f (Get-Date))  
  98.     $ErrorTracker = 'without errors'  
  99.   
  100.     ForEach($SubFolder in (Get-ChildItem $SourcePath -Directory))   
  101.       {  
  102.       $DestSubFolder = Join-Path -Path $DestinationPath -ChildPath $SubFolder  
  103.       if(!(Test-Path $DestSubFolder))  
  104.         {  
  105.         Try {  
  106.           [void] (New-Item -path $DestinationPath -name $SubFolder -ItemType directory -Force -ErrorAction Stop)  
  107.           }  
  108.         Catch  
  109.           {  
  110.           Write-Warning "Failed creating $SubFolder in $DestinationPath"  
  111.           $ErrorTracker = 'with errors'  
  112.           if ($Log)  
  113.             {  
  114.             Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - Failed creating $SubFolder in $DestinationPath"  
  115.             }  
  116.           } # end Try/Catch  
  117.         }  
  118.       Try {  
  119.           $OldLogs = Get-ChildItem -Path $SubFolder.FullName -Recurse -File -Include *.log -ErrorAction Stop |  
  120.             Where-Object { Test-Path -Path $PSItem.FullName -OlderThan (Get-Date).AddDays(-$Days) }  
  121.           }  
  122.       Catch  
  123.           {  
  124.           Write-Warning "An error occurred retrieving logs from $($SubFolder.fullname)"  
  125.           $ErrorTracker = 'with errors'  
  126.           if ($Log)  
  127.             {  
  128.             Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - An error occurred retrieving logs from $($SubFolder.fullname)"  
  129.             }  
  130.           } # end Try/Catch  
  131.       if($OldLogs)  
  132.         {  
  133.         $OldLogs | Foreach-Object {  
  134.           Try {  
  135.             $CurrentLog = $PSItem  
  136.             Write-Verbose "Archiving $($CurrentLog.name) [$($CurrentLog.lastWriteTime)]"  
  137.             $CurrentLog | Move-Item -Destination $DestSubFolder -Force -ErrorAction Stop  
  138.             }  
  139.           Catch {  
  140.             Write-Warning "An error occurred moving $($CurrentLog.fullname)"  
  141.             $ErrorTracker = 'with errors'  
  142.           if ($Log)  
  143.             {  
  144.             Add-Content -LiteralPath $FailedLogFile -Value "$(Get-Date) - An error occurred moving $($CurrentLog.fullname)"  
  145.             }  
  146.             } # end Try/Catch  
  147.           }  
  148.         }  
  149.       } # end ForEach-Object  
  150.     Write-Verbose ("Script finished processing $ErrorTracker at {0:hh}:{0:mm}:{0:ss}" -f (Get-Date))  
  151.     if($ViewErrorLog)  
  152.       {  
  153.       & $FailedLogFile  
  154.       }  
  155.   } # end Function Archive-Logs  
Apparently everything is well. Unfortunately there are a few mistakes that make me score 2.8 out of 5 possible points.

Let's start with the biggest mistake, which is: 
  1. Get-ChildItem -Path $SubFolder.FullName -Recurse -File -Include *.log -ErrorAction Stop  
The problem here is that I have misinterpreted the use of Get-ChildItem. When I tell this cmdlet to Recurse, I am going to return also files which are stored in any subdirectory. At the same time if I omitted this switch, no files where returned.

The solution to this, I give credit to Mike for this, was to include the desired file extension into the Path parameter:
  1. Get-ChildItem -Path (Join-Path -Path $SourcePath -ChildPath '*\*.log') -ErrorAction Stop  
The lesson I get from this is always to read the cmdlet full help, even if I am pretty sure of how it works.

Another big error of mine was not to comment enough. Many people stated that my script was hard to follow. The fact is that I deemed unnecessary to explain how my code worked to people who already have a proficient knowledge of Powershell. I was wrong.

The lesson I get from this is always to put comments before complicated lines of code as well as the end of each scriptblock. Here's two examples:
  1.  foreach ($Computer in $ComputerName) {  
  2. ... any action ...  
  3. # end of foreach $Computer
or
  1. if($pingworks){  
  2. get-wmiobject...  
  3. # end of If block  
This makes my script much more readable.

The final thing that I learned from Event 1 and that I want to share with you is never to use superflous code. An example could be specifying that a parameters is not mandatory:
  1. [Parameter(Mandatory=$False]  
This is unneded in Powershell. So better to omit it.

That's all for the for Event 1. If you want to see it on the ScriptingGames website click on the image. Unfortunately is too late to vote for my entry on this assignment.

In the next post I will talk about the Second Event of the Games. Stay tuned!
Related Posts Plugin for WordPress, Blogger...