Pablo's Powershell Pow-Wow

Discussion in 'Business & Enterprise Computing' started by PabloEscobar, Sep 18, 2014.

  1. Embercide

    Embercide Member

    Joined:
    Jun 17, 2002
    Messages:
    1,818
    Location:
    Brisbane
    Subscribed :thumbup:
    All of my scripting tasks are currently a bastardised frankenstein of powershell, batch files, powershell that call cmd /c commands, and batch files that launch powershell scripts :lol:
     
  2. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Cheers for that, I've linked it in the first post, let me know if you want the wording changed or similar.


    Any reasons for the cmd /c prefix? from powershell, you can call anything in the path natively, which lets us do terrible things like

    Code:
    [string]$pings = ping 127.0.0.1
    $pings.contains("Lost = 0")
    
    Which takes the output of the ping command, which we are all familiar with

    Code:
    Pinging 127.0.0.1 with 32 bytes of data:
    Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
    Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
    Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
    Reply from 127.0.0.1: bytes=32 time<1ms TTL=128
    
    Ping statistics for 127.0.0.1:
        Packets: Sent = 4, Received = 4, [B]Lost = 0[/B] (0% loss),
    Approximate round trip times in milli-seconds:
        Minimum = 0ms, Maximum = 0ms, Average = 0ms
    
    And looks for "Lost = 0" as confirmation of connectivity.

    The amount of batch scripts I've got that use a ping test and Find to determine connectivity is rather high, so naturally, when I moved to powershell, this just carried over (although not using find any more, because String methods are so much easier).

    But, powershell has test-connection which achieves the same thing, and newer versions have test-netconnection which is even better, because instead of checking icmp echo connectivity, I can check that the port I need to talk via is actually ready to accept me.
     
  3. Embercide

    Embercide Member

    Joined:
    Jun 17, 2002
    Messages:
    1,818
    Location:
    Brisbane
    A few reasons, main one being I would have been in a rush at the time and not known the PS equivalent of a command, or I may have a snippet of 'code' in another batch file already so I'll copy and paste it into a function in a PS script i'm working on.

    I've also had some problems with PS and scripting silent installs for installshield installers that seem to work fine in a .bat
     
  4. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,078
    Location:
    NSW Central Coast
    Ooh, add Start-Transcript and Stop-Transcript to the list of items to discuss, Pablo :)

    I've just found a bug in my Backup Exec control script using the transcript. It's a pretty simple script (runs a stack of jobs sequentially to ensure appending and no disk contention, pulls data out of each job history for a consolidated report, then exports the tape to the mailslot). It has failed to export the past week - see if this transcript helps:

    Code:
    Exporting tape because it's Tuesday
    .\Export-MediaLabel.ps1 _ 000001L6
     _
    Searching libraries for the media with label "000001L6
    "
    WARNING: The tape with MediaID 000001L6
     could not be found. This may occur if the tape is not present, or has already 
    been exported.
    No? Here's what the log SHOULD look like:
    Code:
    Exporting tape because it's Tuesday
    .\Export-MediaLabel.ps1 _ 000001L6 _
    Searching libraries for the media with label "000001L6"
    WARNING: The tape with MediaID 000001L6 could not be found. This may occur if the tape is not present, or has already 
    been exported.
    Yeah. The sodding media label has a newline at the end!
     
  5. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Never fear, I've not forgotten about this thread, But I have had some issues with the PC my Lab runs on, which is worthy of an article on its own, but I had some strangeness with the ISE that might be worth discussing...

    I'm moving a few VM's between disks on my Test lab using Move-VMStorarge

    I open a powershell window and run

    Code:
    Move-VMStorage "MBX01" -desitnationstoragepath F:\LAB\VM\MBX01
    While the move is in process, I've opened up a Powershell ISE window, to check on the paths of some other VM's

    Code:
    Get-VM | Select Name,Path
    
    PS C:\Users\Administrator> get-vm | select Name,Path
    
    
    Name                                                                                 Path                                                                                
    ----                                                                                 ----                                                                                
    CAS01                                                                                F:\Lab\VM\CAS01                                                                     
    ChildDC01                                                                            F:\Lab\VM\ChildDC01                                                                 
    DC01                                                                                 F:\Lab\VM\DC01                                                                      
    DC02                                                                                 F:\LAB\VM\DC02                                                                      
    MBX01                                                                                E:\MBX01                                                                            
    PFSense                                                                              F:\Lab\VM\PFSense                                                                   
    SCO                                                                                  F:\Lab\VM\SCO                                                                       
    Server2008                                                                           F:\Lab\VM\Server2008                                                                
    Sharepoint                                                                           F:\Lab\VM\Sharepoint                                                                
    TEST-Win2012r2                                                                       F:\Lab\VM\Test-Win2012R2                                                            
    Test-Win7                                                                            F:\Lab\VM\Test-Win7                                                                 
    Test-XP                                                                              F:\Lab\VM\TEST-XP           
    
    All hunky dory, VM's are all in their right places, MBX01 still shows as E:\MBX01 because the move hasn't completed yet...

    Move Completes, In my original powershell window (where I was running move-vmstorage), I check the path of my freshly moved VM...

    Code:
    PS C:\Users\Administrator> get-vm MBX01 | select name,path
    
    Name                                                        Path
    ----                                                        ----
    MBX01                                                       F:\LAB\VM\MBX01
    
    Makes sense, we've just moved our VM to its new location... BUT... when I run that same command in the ISE window...

    Code:
    PS C:\Users\Administrator> get-vm MBX01 | select name,path
    
    Name                                                                                 Path                                                                                
    ----                                                                                 ----                                                                                
    MBX01                                                                                E:\MBX01          
    I get a different response,

    I'm not familiar with the Powershell ISE, but this does not strike me as expected behavior? Does the ISE cache the output of commands and then just return it, Short of Closing and Reopening the ISE, is there any way I can resolve this, and have my Get-VM call return CURRENT information?
     
  6. Great_Guru

    Great_Guru Member

    Joined:
    Sep 5, 2001
    Messages:
    1,225
    Location:
    Australia
    I came across a similar issue of cached output with ISE. I stopped using it after a very short period of time as I was a PowerShell noobie and I didn't need quirks like that interfering with my very limited grasp of PS. Maybe someone with more knowledge can shed the light.

    Thank you for starting this series by the way.

    I always try to think to myself when using a GUI tool, do I know how to do this in PS? if no and time is available I try to nut it out and learn on the spot. The problem is time isn't that plentiful with other pressing matters landing on my desk.
     
  7. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Disk Usage Report - Something Useful at Last

    Pablo, I hear you whine, you promised me that I could do tonnes of cool useful stuff in powershell and it would make my life easier, so far... I've been harping on about objects for the first few entries, purely because it was the hardest thing for me to wrap my head around, and is one of the key concepts of powershell... so, now that we all know "EVERYTHING IS AN OBJECT" lets get into doing some useful and fun shit with our Objects...

    Our last example showed us how to delete all files older than a certain amount of days, which is great, but for it to be useful, we need to know how full our disks are, so lets do something about that.

    Windows makes a whole stack of information available via WMI (Windows Management Instrumentation), and powershell provides Get-WmiObject as cmdlet for grabbing that information, A program like WMI Explorer (https://wmie.codeplex.com/) is great for exploring the WMI namespace, In the case of disk details "Win32_LogicalDisk" is the one we want to play with.

    Lets see what we know.

    Code:
    Get-WmiObject Win32_LogicalDisk
    
    DeviceID     : C:
    DriveType    : 3
    ProviderName :
    FreeSpace    : 10151202816
    Size         : 20920135680
    VolumeName   : First Disk
    
    DeviceID     : D:
    DriveType    : 5
    ProviderName :
    FreeSpace    : 0
    Size         : 4100497408
    VolumeName   : JM1_CCSA_X64FRE_EN-US_DV9
    
    DeviceID     : E:
    DriveType    : 3
    ProviderName :
    FreeSpace    : 4190814208
    Size         : 5333053440
    VolumeName   : Second Disk
    
    
    Great, we can see our disks, we only need to worry about Drives of Type 3, so we can trim it using -filter "DriveType = 3" (we could also pipe it to where-object, but as a rule, -filter is normally faster)

    So lets roll with that, we will also pipe it to "Format-List *" so we can see more of the properties we can play with later...

    Code:
    Get-WmiObject Win32_LogicalDisk -filter "DriveType = 3" | FL *
    
    PSComputerName               : TEST-WIN10
    Status                       :
    Availability                 :
    DeviceID                     : C:
    StatusInfo                   :
    __GENUS                      : 2
    __CLASS                      : Win32_LogicalDisk
    __SUPERCLASS                 : CIM_LogicalDisk
    __DYNASTY                    : CIM_ManagedSystemElement
    __RELPATH                    : Win32_LogicalDisk.DeviceID="C:"
    __PROPERTY_COUNT             : 40
    __DERIVATION                 : {CIM_LogicalDisk, CIM_StorageExtent, CIM_LogicalDevice, CIM_LogicalElement...}
    __SERVER                     : TEST-WIN10
    __NAMESPACE                  : root\cimv2
    __PATH                       : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
    Access                       : 0
    BlockSize                    :
    Caption                      : C:
    Compressed                   : False
    ConfigManagerErrorCode       :
    ConfigManagerUserConfig      :
    CreationClassName            : Win32_LogicalDisk
    Description                  : Local Fixed Disk
    DriveType                    : 3
    ErrorCleared                 :
    ErrorDescription             :
    ErrorMethodology             :
    FileSystem                   : NTFS
    FreeSpace                    : 10151202816
    InstallDate                  :
    LastErrorCode                :
    MaximumComponentLength       : 255
    MediaType                    : 12
    Name                         : C:
    NumberOfBlocks               :
    PNPDeviceID                  :
    PowerManagementCapabilities  :
    PowerManagementSupported     :
    ProviderName                 :
    Purpose                      :
    QuotasDisabled               : True
    QuotasIncomplete             : False
    QuotasRebuilding             : False
    Size                         : 20920135680
    SupportsDiskQuotas           : True
    SupportsFileBasedCompression : True
    SystemCreationClassName      : Win32_ComputerSystem
    SystemName                   : TEST-WIN10
    VolumeDirty                  : False
    VolumeName                   : First Disk
    VolumeSerialNumber           : BAE15557
    Scope                        : System.Management.ManagementScope
    Path                         : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk.DeviceID="C:"
    Options                      : System.Management.ObjectGetOptions
    ClassPath                    : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk
    Properties                   : {Access, Availability, BlockSize, Caption...}
    SystemProperties             : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
    Qualifiers                   : {dynamic, Locale, provider, UUID}
    Site                         :
    Container                    :
    
    PSComputerName               : TEST-WIN10
    Status                       :
    Availability                 :
    DeviceID                     : E:
    StatusInfo                   :
    __GENUS                      : 2
    __CLASS                      : Win32_LogicalDisk
    __SUPERCLASS                 : CIM_LogicalDisk
    __DYNASTY                    : CIM_ManagedSystemElement
    __RELPATH                    : Win32_LogicalDisk.DeviceID="E:"
    __PROPERTY_COUNT             : 40
    __DERIVATION                 : {CIM_LogicalDisk, CIM_StorageExtent, CIM_LogicalDevice, CIM_LogicalElement...}
    __SERVER                     : TEST-WIN10
    __NAMESPACE                  : root\cimv2
    __PATH                       : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk.DeviceID="E:"
    Access                       : 0
    BlockSize                    :
    Caption                      : E:
    Compressed                   : False
    ConfigManagerErrorCode       :
    ConfigManagerUserConfig      :
    CreationClassName            : Win32_LogicalDisk
    Description                  : Local Fixed Disk
    DriveType                    : 3
    ErrorCleared                 :
    ErrorDescription             :
    ErrorMethodology             :
    FileSystem                   : NTFS
    FreeSpace                    : 4190814208
    InstallDate                  :
    LastErrorCode                :
    MaximumComponentLength       : 255
    MediaType                    : 12
    Name                         : E:
    NumberOfBlocks               :
    PNPDeviceID                  :
    PowerManagementCapabilities  :
    PowerManagementSupported     :
    ProviderName                 :
    Purpose                      :
    QuotasDisabled               : True
    QuotasIncomplete             : False
    QuotasRebuilding             : False
    Size                         : 5333053440
    SupportsDiskQuotas           : True
    SupportsFileBasedCompression : True
    SystemCreationClassName      : Win32_ComputerSystem
    SystemName                   : TEST-WIN10
    VolumeDirty                  : False
    VolumeName                   : Second Disk
    VolumeSerialNumber           : F2DDB21D
    Scope                        : System.Management.ManagementScope
    Path                         : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk.DeviceID="E:"
    Options                      : System.Management.ObjectGetOptions
    ClassPath                    : \\TEST-WIN10\root\cimv2:Win32_LogicalDisk
    Properties                   : {Access, Availability, BlockSize, Caption...}
    SystemProperties             : {__GENUS, __CLASS, __SUPERCLASS, __DYNASTY...}
    Qualifiers                   : {dynamic, Locale, provider, UUID}
    Site                         :
    Container                    :
    
    Theres a stack of properties available for us, so lets make it a bit more readable, and have a look at the ones that matter to us...

    Code:
    Get-WmiObject Win32_LogicalDisk -filter "DriveType = 3" | FT VolumeName,DeviceID,FreeSpace,Size,[B]@{Name="PercentFree";Expression={[decimal]::round(($_.FreeSpace/$_.size)*100)}}[/B] -AutoSize
    
    VolumeName  DeviceID   FreeSpace        Size PercentFree
    ----------  --------   ---------        ---- -----------
    First Disk  C:       10152648704 20920135680          49
    Second Disk E:        4190814208  5333053440          79
    
    A few new things here, firstly @{Name;Expression} this is whats known as a calculated property, and is exactly what it sounds like, we are creating a new property called "PrecentFree" and in it, we are storing the result of the expression "[decimal]::(FreeSpace/Size) * 100". the [decimal]:: is new to us as well. Powershell lets us easily access the .Net framework to do stuff, Decimal is a powershell class, that has some useful number manipulation methods, in this case, "round" to let us round our numbers to integers to make them more user friendly.

    With our new knowledge of calculated properties, we can neaten the whole thing up.

    Code:
    Get-WmiObject Win32_LogicalDisk -filter "DriveType = 3" | FT VolumeName,DeviceID,@{Name="FreeGB";Expression={[math]::round($_.FreeSpace / 1GB,2)}},@{Name="TotalGB";Expression={[math]::round($_.Size / 1GB,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}} -AutoSize
    
    
    VolumeName  DeviceID FreeGB TotalGB PercentFree
    ----------  -------- ------ ------- -----------
    First Disk  C:         9.47   19.48       48.59
    Second Disk E:          3.9    4.97       78.58
    
    And we are pretty much done, I've changed our [decimal] class out for a more suited [math] class, which lets us specify how many decimal places we want to round to. (2 in this case)

    "That's great Pablo, but in the time its taken you to write this, I could have glanced at Windows explorer, and given you those figures straight away", and you'd be correct, but now, when BossMan asks for a report on every computer in the domain... your method doesn't scale at all, mine does, with the use of our good friend, the loop...

    Code:
    FOR /F "delims=" %%i IN (C:\scripts\computers.txt) DO (
     Someshite "%%i"
    )
    
    Many of use will have batch files that will have that, or something similar in it, and often we will need to perform similar actions (Someshite) on a number of computers, so lets look at how to do that in powershell.

    Code:
    get-content "C:\Scripts\Computers.txt" | ForEach-Object { Write-Host $_ }
    
    MBX01
    CAS01
    SERVER2008
    TEST-WIN2012R2
    TEST-WIN10
    DC01
    DC02
    
    Is how its done in powershell, Get-Content reads in the file, pushes it to ForEach-Object, $_ notation can be a bit confusing when you start getting into nested loops, so for easy of readability, I'll often write this command as

    Code:
    $Computers = get-content "C:\Scripts\Computers.txt" 
    foreach ($Computer in $Computers)
      	{
    	Write-Host $Computer
    	}
    
    MBX01
    CAS01
    SERVER2008
    TEST-WIN2012R2
    TEST-WIN10
    DC01
    DC02
    
    $computers = collection
    $computer = item

    We fetch our content and store in in the $computers variable, and then we use the foreach statement to iterate each Entry in the list ,and store it in the $computer variable, this also has the benefit of being able to easily see and change where we fetch our list from (In reality, I'll be pulling a list of computers from Active directory using Get-ADComputer, but that relies on modules, which we might go into next week)

    OK, we've got our list, so now we just need to add a -ComputerName parameter to our Get-WmiObject cmdlet and see what happens...

    Code:
    $Computers = get-content "C:\Scripts\Computers.txt" 
    foreach ($Computer in $Computers)
      	{
    	Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter "DriveType = 3" | FT VolumeName,DeviceID,@{Name="FreeGB";Expression={[math]::round($_.FreeSpace / 1GB,2)}},@{Name="TotalGB";Expression={[math]::round($_.Size / 1GB,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}} -AutoSize
    	}
    
    
    VolumeName DeviceID FreeGB TotalGB PercentFree
    ---------- -------- ------ ------- -----------
               C:        10.23   39.66       25.81
    
    
    
    VolumeName DeviceID FreeGB TotalGB PercentFree
    ---------- -------- ------ ------- -----------
               C:        22.74   39.66       57.34
    
    
    ERROR: Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
    OCAU-DiskSpace.ps1 (12): ERROR: At Line: 12 char: 2
    ERROR: +     Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter " ...
    ERROR: +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ERROR:     + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], COMException
    ERROR:     + FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
    ERROR:
    ERROR: Get-WmiObject : The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)
    OCAU-DiskSpace.ps1 (12): ERROR: At Line: 12 char: 2
    ERROR: +     Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter " ...
    ERROR: +     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    ERROR:     + CategoryInfo          : InvalidOperation: (:) [Get-WmiObject], COMException
    ERROR:     + FullyQualifiedErrorId : GetWMICOMException,Microsoft.PowerShell.Commands.GetWmiObjectCommand
    ERROR:
    
    VolumeName  DeviceID FreeGB TotalGB PercentFree
    ----------  -------- ------ ------- -----------
    First Disk  C:         8.99   19.48       46.17
    Second Disk E:          3.9    4.97       78.58
    
    
    
    VolumeName DeviceID FreeGB TotalGB PercentFree
    ---------- -------- ------ ------- -----------
               C:        26.37   39.66       66.49
    
    
    
    VolumeName DeviceID FreeGB TotalGB PercentFree
    ---------- -------- ------ ------- -----------
               C:        29.52   39.66       74.43
    
    So from this, you'll notice a few things, There are a few computers in my test lab that are turned off at the moment, so they are returning with "The RPC server is unavailable", you will also notice that we don't actually get the computer name anywhere in our results, so its kind of hard to decipher which result it for which PC, and lastly, I don't tend to use volume names on my test lab, so lets tidy up these 3 things.

    Code:
    $Computers = get-content "C:\Scripts\Computers.txt" 
    foreach ($Computer in $Computers)
      	{
    	if (Test-Connection -ComputerName $Computer -Count 1 -Quiet)
    		{
    		Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter "DriveType = 3" | FT $Computer,DeviceID,@{Name="FreeGB";Expression={[math]::round($_.FreeSpace / 1GB,2)}},@{Name="TotalGB";Expression={[math]::round($_.Size / 1GB,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}} -AutoSize
    		}
    	else {Write-Host "$Computer Is Offline"}
    	}	
    
    MBX01 DeviceID FreeGB TotalGB PercentFree
    ----- -------- ------ ------- -----------
          C:         9.65   39.66       24.34
    
    
    
    CAS01 DeviceID FreeGB TotalGB PercentFree
    ----- -------- ------ ------- -----------
          C:        22.73   39.66       57.32
    
    
    SERVER2008 Is Offline
    TEST-WIN2012R2 Is Offline
    
    TEST-WIN10 DeviceID FreeGB TotalGB PercentFree
    ---------- -------- ------ ------- -----------
               C:         8.99   19.48       46.16
               E:          3.9    4.97       78.58
    
    
    
    DC01 DeviceID FreeGB TotalGB PercentFree
    ---- -------- ------ ------- -----------
         C:        26.36   39.66       66.47
    
    
    
    DC02 DeviceID FreeGB TotalGB PercentFree
    ---- -------- ------ ------- -----------
         C:        29.51   39.66       74.41
    
    Its looking much better now, we can see disk space for the computers that were reachable, and we know which ones we couldn't reach.

    Powrshell conditional logic looks like

    Code:
    If (statement) {Do Shite}
    elseif {Do other Shite}
    else {Do some different Shite}
    and test-connection is powershells version of ping, so all we are doing is checking that "Test-Connection -ComputerName $Computer -Count 1 -Quiet" (-count 1 to make it quicker, -Quiet to not print anything on the screen) returns 1, and if it does, we can Get-WmiObject the computer... if we can't reach it with test-connection, then we print it to the screen as part of our "else" statement.

    We have a pretty usable script right now, stay tuned to our next exciting episode, where we make it Boss friendly (via HTML Email) and setup some threshold alerting.
     
  8. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    The Above script is great, but nobody wants to be manually running a powershell script on a daily basis, so how about we make it much more PHB friendly, and have it E-mail the report to him. This way, it appears I'm doing heaps of work, when in reality, a scheduled powershell script is doing it for me.

    So without further ado - here is our new-look script with added e-mail goodness... (keep it handy, as I might build on it some more).


    Code:
    [B]#Mail Details
    $mailto = "Pablo@whyadmin.com"
    $mailfrom = "OCAUDiskCheck@whyadmin.com"
    $smtpserver = "cas01.corp.whyadmin.com"
    $mailSubject = "Disk Space Check - Report $(Get-Date)"
    
    
    
    $HTML = '<style type="text/css">'
    $HTML += 'TABLE{border-collapse: collapse;border-width: 1px;border-style: solid;border-color:black;border-spacing: 10px;}'
    $HTML += 'TH{border-width:1px;padding:0px;border-style:solid;border-color:black;text-align:center;border-spacing: 10px;}'
    $HTML += 'TD{border-width:1px;padding-left:5px;border-style:solid;border-color:black;text-align:left;}'
    $HTML += "</style>"
    [/B]
    
    $Computers = get-content "C:\Scripts\Computers.txt" 
    foreach ($Computer in $Computers)
      	{
    	if (Test-Connection -ComputerName $Computer -Count 1 -Quiet)
    		{
    		[B]$DiskSpace = [/B]Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter "DriveType = 3" | [B]Select-Object[/B] $Computer,DeviceID,@{Name="FreeGB";Expression={[math]::round($_.FreeSpace / 1GB,2)}},@{Name="TotalGB";Expression={[math]::round($_.Size / 1GB,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}}
    		[B]$HTML += ($DiskSpace | ConvertTo-Html -Fragment)
    		$HTML += "<BR>"[/B]
    		}
    	else {[B]$html += "$Computer Is Offline<P>"[/B]}
    	}
    
    [B]
    Send-MailMessage  -To $mailto -From $mailfrom -Subject $mailSubject -BodyAsHtml $html -SmtpServer $smtpserver[/B]
    

    At the top, we've just chucked some specifics into variables, to make it easier to change down the track.

    The next thing we do is setup our $html variable, which will eventually hold our output. += is an "assignment operator", It just appends whatever is after it, to whatever the variable currently holds. Because its powershell, theres always muliple ways of doing things.

    Code:
    $a = "foo"
    $a = $a + "bar"
    
    [I]Is the same as[/I]
    
    $a = "foo"
    $a += "bar"
    
    I'm not a HTML wizard, and my CSS is probably terrible, but we setup some Style for our table, which comes later.


    You will notice that we have changed our "| FT" from Format-Table to Select-Object, and instead of outputing it, we are storing it in the $diskspace variable. Format Table shouldn't really be used (for the same reasons as write-host) in scripts that are destined for automation, as it essentially breaks the pipeline, you can't effectivley pass the output of Format-Table to anything, its purely used for displaying something nicely in the console.

    Our next change is to take our newly create $diskpace variable (which, because its powershell, is actually an object), and Pipe (|) it to Convertto-HTML. This cmdlet takes an objects, and outputs a HTML table - we add this result to our $HTML variable (again, using +=). We do something similar with the output for the else branch of our if statement

    So, now we have our HTML formatting all stored in our $HTML variable, and we need to send it. In the days of Dos, we'd shell out to something like Blat, but in powershell, we have access to send-mailmessage which we feed our variables to as parameters, and Boom... a freshly formatted Disk space report, delivered daily to your inbox.
     
  9. dred0r

    dred0r Member

    Joined:
    May 23, 2002
    Messages:
    159
    Thanks for this thread!
    Now I just need to put it to some use.
     
  10. somebloke

    somebloke Member

    Joined:
    Feb 4, 2002
    Messages:
    217
    Location:
    Perth
    *bookmarking thread*

    I like to add (in the ? filter) -and $_.psiscontainer –eq $false. Im not sure if its needed, but you don't always want subdirs removed (not too sure if last write time would capture this or if -recurse would need to be set for a subdir to get removed. hmm, something to test!)

    can also just pipe to % {$_.delete()}

    You can also use -filter in the get-childitem cmd-let if more than just log files are present in SomeCrappyApp (tm)'s log dir

    Get-ChildItem -filter "*.log" | blah etc
     
  11. Tekin

    Tekin Member

    Joined:
    Nov 16, 2002
    Messages:
    4,039
    Location:
    Elsewhere.
    Awesome thread!

    Moved into powershell when I started hitting the absolute limits of batch (like. seriously. the absolute outer limits - it's pretty bad).

    I've actually got a really specific problem I'm dealing with at the moment, it's definitely something pretty easy I think (parsing a string into objects) but it's been doing my head in! I'll write it up when I get home for the gurus in ocau!
     
  12. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    For those playing along at home

    % is an alias of ForEach-Object, and ? is an Alias for Where-Object :). (See prior posts for my reasoning against using them in anything other than one-liners).

    Regarding use of $_.delete()...

    Get-ChildItem can be used for more than just listing a folder or directory. In powershell, lots of Objects can be enumerated using it, and they don't all have the Delete method.

    Powershell can use more than just a filesystem as a "Drive", as evidenced by Get-PSDrive

    Code:
    Get-PSDrive
    Name           Used (GB)     Free (GB) Provider      Root                                                                                       CurrentLocation
    ----           ---------     --------- --------      ----                                                                                       ---------------
    Alias                                  Alias
    C                  14.68          4.80 FileSystem    
    Cert                                   Certificate   \
    D                    .71               FileSystem    D:\
    E                    .13          4.83 FileSystem    E:\
    Env                                    Environment
    Function                               Function
    HKCU                                   Registry      HKEY_CURRENT_USER
    HKLM                                   Registry      HKEY_LOCAL_MACHINE
    Variable                               Variable
    WSMan                                  WSMan
    
    Here we can see our filesystems connected as drives, but we've also got things like our Cert Store, and Registry which are also "Drives" as far as powershell goes. So If I wanted to, I can list all environment variables by using Get-ChildItem on the Env: drive...

    Code:
    Get-ChildItem Env:
    
    Name                           Value
    ----                           -----
    ALLUSERSPROFILE                C:\ProgramData
    APPDATA                        C:\Users\administrator\AppData\Roaming
    CLIENTNAME                     MICRO
    CommonProgramFiles             C:\Program Files\Common Files
    CommonProgramFiles(x86)        C:\Program Files (x86)\Common Files
    CommonProgramW6432             C:\Program Files\Common Files
    COMPUTERNAME                   TEST-WIN10
    ComSpec                        C:\Windows\system32\cmd.exe
    FPS_BROWSER_APP_PROFILE_STRING Internet Explorer
    FPS_BROWSER_USER_PROFILE_ST... Default
    HOMEDRIVE                      C:
    HOMEPATH                       \Users\administrator
    LOCALAPPDATA                   C:\Users\administrator\AppData\Local
    LOGONSERVER                    \\DC01
    NUMBER_OF_PROCESSORS           1
    OS                             Windows_NT
    Path                           C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\
    PATHEXT                        .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL
    PROCESSOR_ARCHITECTURE         AMD64
    PROCESSOR_IDENTIFIER           AMD64 Family 21 Model 2 Stepping 0, AuthenticAMD
    PROCESSOR_LEVEL                21
    PROCESSOR_REVISION             0200
    ProgramData                    C:\ProgramData
    ProgramFiles                   C:\Program Files
    ProgramFiles(x86)              C:\Program Files (x86)
    ProgramW6432                   C:\Program Files
    PSModulePath                   C:\Users\administrator\Documents\WindowsPowerShell\Modules;C:\Program Files (x86)\WindowsPowerShell\Modules;C:\Windows\system...
    PUBLIC                         C:\Users\Public
    SESSIONNAME                    RDP-Tcp#1
    SystemDrive                    C:
    SystemRoot                     C:\Windows
    TEMP                           C:\Users\ADMINI~1\AppData\Local\Temp
    TMP                            C:\Users\ADMINI~1\AppData\Local\Temp
    USERDNSDOMAIN                  CORP.WHYADMIN.COM
    USERDOMAIN                     CORP
    USERDOMAIN_ROAMINGPROFILE      CORP
    USERNAME                       administrator
    USERPROFILE                    C:\Users\administrator
    windir                         C:\Windows
    
    But, If I try to delete one of these using the Delete() method, I get an error

    Code:
    Get-ChildItem Env: | Where-Object {$_.Name -eq "COMPUTERNAME"} | % {$_.delete()}
    
    ERROR: Method invocation failed because [System.Collections.DictionaryEntry] does not contain a method named 'delete'.
    OCAU-GetPSDrive.ps1 (1): ERROR: At Line: 1 char: 69
    ERROR: + ... em Env: | Where-Object {$_.Name -eq "COMPUTERNAME"} | % {$_.delete()}
    ERROR: +                                                              ~~~~~~~~~~~
    ERROR:     + CategoryInfo          : InvalidOperation: (:) [], RuntimeException
    ERROR:     + FullyQualifiedErrorId : MethodNotFound
    ERROR:
    
    whereas piping it to Remove-Item, works correctly regardless of the objects that Get-ChildItem is fetching. (assuming they can be removed)
     
  13. somebloke

    somebloke Member

    Joined:
    Feb 4, 2002
    Messages:
    217
    Location:
    Perth
    Guess who is switching to Remove-Item
     
  14. Eddyah

    Eddyah Member

    Joined:
    Aug 12, 2005
    Messages:
    1,647
    Random question which I never ended up solving but OP (or others) might know:

    Is it possible to go from a .zip file to base64 binary then back to .zip?

    When I tried doing this, I found the .zip I ended up with became corrupt.

    When I was doing my debugging, I found there was an issue with the Get-Content function I was using to read in the zip file (as I read it, then wrote it to file and the file written was a corrupt archive). To my disappointment, there is no standardised way to encode a zip file which I believe was the problem.
     
  15. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Powershell V5 (http://blogs.msdn.com/b/powershell/...-preview-september-2014-is-now-available.aspx) include native cmdlets for zipping (compress-archive) and unzipping (expand-archive), which will probably make life easier.

    Alternatively you can use the Shell Com object to make windows do it for you. (Com objects might be a good subject for this weeks article :))

    I'm not sure on the base64 side of things - but http://mnaoumov.wordpress.com/2013/08/20/efficient-base64-conversion-in-powershell/ seems to have some functions that use the Windows System.IO and Convert calls for manipulating the file rather than using get-content.

    Get-content is pretty smart, but I still expect it to shit itself if you feed it a binary.
     
  16. Draxx

    Draxx Member

    Joined:
    Jun 27, 2001
    Messages:
    1,053
    Location:
    Bendigo
     
  17. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Strange, The script was created and tested on a Win10 machine with Powershell Studio, but I've just tested it using a Win7 machine and Powershell ISE, and both with and without the quotes, I get a (badly) HTML formatted e-mail sent to me.

    Get-Content gets the content (strange that) of the file... and (for me at least) passes it along as a string.

    Is this the same for you? we can quickly see what we are getting by using Get-Member

    Code:
    
    $Computers = get-content "C:\Scripts\Computers.txt" 
    $computers | Get-Member
    
    
    
      [B] TypeName: System.String[/B]
    
    Name             MemberType            Definition                                                                                                                        
    ----             ----------            ----------                                                                                                                        
    Clone            Method                System.Object Clone(), System.Object ICloneable.Clone()                                                                           
    CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB), int IComparable.CompareTo(System.Object obj), int IComparable[s...
    Contains         Method                bool Contains(string value)                                                                                                       
    CopyTo           Method                void CopyTo(int sourceIndex, char[] destination, int destinationIndex, int count)                                                 
    EndsWith         Method                bool EndsWith(string value), bool EndsWith(string value, System.StringComparison comparisonType), bool EndsWith(string value, b...
    blah blah blah
    
    Is your computers.txt file just a plain text file with a list of computers in it, one per line?
     
  18. Draxx

    Draxx Member

    Joined:
    Jun 27, 2001
    Messages:
    1,053
    Location:
    Bendigo
    Without the quotes I get errors as follows:
    Code:
    Select-Object : Cannot convert System.Management.Automation.PSObject to one of the following types {System.String, Syst
    em.Management.Automation.ScriptBlock}.
    At D:\scripts\check-email.ps1:21 char:111
    +         $DiskSpace = Get-WmiObject Win32_LogicalDisk -ComputerName $Computer -filter "DriveType = 3" | Select-object
    <<<<  $Computer,DeviceID,@{Name="FreeGB";Expression={[math]::round($_.FreeSpace / 1GB,2)}},@{Name="TotalGB";Expression=
    {[math]::round($_.Size / 1GB,2)}},@{Name="PercentFree";Expression={[math]::round(($_.FreeSpace/$_.size)*100,2)}}
        + CategoryInfo          : InvalidArgument: (:) [Select-Object], NotSupportedException
        + FullyQualifiedErrorId : DictionaryKeyUnknownType,Microsoft.PowerShell.Commands.SelectObjectCommand
    I do receive an email, but it just has an empty table with only one server name, that being one that is offline right now.

    With the quotes I get the (badly) formatted HTML email :)

    Code:
    $Computers = get-content "D:\Scripts\Computers.txt"
    $computers | Get-Member
    
    
       TypeName: System.String
    
    Name             MemberType            Definition
    ----             ----------            ----------
    Clone            Method                System.Object Clone()
    CompareTo        Method                int CompareTo(System.Object value), int CompareTo(string strB)
    Contains         Method                bool Contains(string value)
    CopyTo           Method                System.Void CopyTo(int sourceIndex, char[] destination, int destinationIndex,...
    EndsWith         Method                bool EndsWith(string value), bool EndsWith(string value, System.StringCompari...
    Equals           Method                bool Equals(System.Object obj), bool Equals(string value), bool Equals(string...
    GetEnumerator    Method                System.CharEnumerator GetEnumerator()
    GetHashCode      Method                int GetHashCode()
    GetType          Method                type GetType()
    GetTypeCode      Method                System.TypeCode GetTypeCode()
    IndexOf          Method                int IndexOf(char value), int IndexOf(char value, int startIndex), int IndexOf...
    IndexOfAny       Method                int IndexOfAny(char[] anyOf), int IndexOfAny(char[] anyOf, int startIndex), i...
    Insert           Method                string Insert(int startIndex, string value)
    IsNormalized     Method                bool IsNormalized(), bool IsNormalized(System.Text.NormalizationForm normaliz...
    LastIndexOf      Method                int LastIndexOf(char value), int LastIndexOf(char value, int startIndex), int...
    LastIndexOfAny   Method                int LastIndexOfAny(char[] anyOf), int LastIndexOfAny(char[] anyOf, int startI...
    Normalize        Method                string Normalize(), string Normalize(System.Text.NormalizationForm normalizat...
    PadLeft          Method                string PadLeft(int totalWidth), string PadLeft(int totalWidth, char paddingChar)
    PadRight         Method                string PadRight(int totalWidth), string PadRight(int totalWidth, char padding...
    Remove           Method                string Remove(int startIndex, int count), string Remove(int startIndex)
    Replace          Method                string Replace(char oldChar, char newChar), string Replace(string oldValue, s...
    Split            Method                string[] Split(Params char[] separator), string[] Split(char[] separator, int...
    StartsWith       Method                bool StartsWith(string value), bool StartsWith(string value, System.StringCom...
    Substring        Method                string Substring(int startIndex), string Substring(int startIndex, int length)
    ToCharArray      Method                char[] ToCharArray(), char[] ToCharArray(int startIndex, int length)
    ToLower          Method                string ToLower(), string ToLower(System.Globalization.CultureInfo culture)
    ToLowerInvariant Method                string ToLowerInvariant()
    ToString         Method                string ToString(), string ToString(System.IFormatProvider provider)
    ToUpper          Method                string ToUpper(), string ToUpper(System.Globalization.CultureInfo culture)
    ToUpperInvariant Method                string ToUpperInvariant()
    Trim             Method                string Trim(Params char[] trimChars), string Trim()
    TrimEnd          Method                string TrimEnd(Params char[] trimChars)
    TrimStart        Method                string TrimStart(Params char[] trimChars)
    PSChildName      NoteProperty          System.String PSChildName=Computers.txt
    PSDrive          NoteProperty          System.Management.Automation.PSDriveInfo PSDrive=D
    PSParentPath     NoteProperty          System.String PSParentPath=D:\Scripts
    PSPath           NoteProperty          System.String PSPath=D:\Scripts\Computers.txt
    PSProvider       NoteProperty          System.Management.Automation.ProviderInfo PSProvider=Microsoft.PowerShell.Cor...
    ReadCount        NoteProperty          System.Int64 ReadCount=1
    Chars            ParameterizedProperty char Chars(int index) {get;}
    Length           Property              System.Int32 Length {get;}
    Testing on Server 2008 R2. Computers.txt file is single line per computer name, standard text file.
     
  19. OP
    OP
    PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,412
    Looks like it might be a foible with Powershell V2.

    check the output of get-host, I've just tried it on 2008R2 and got the same message (and I tried it on a vanilla 7, and also got the message).

    For some reason, powershell v2 doesn't see $computer as a string, despite it obviously being one. By putting quotes around it, you are essentially forcing powershell to evaluate it as a string (which the later versions appear to do naturally).

    You can also force v2 to recognize it as a string by prefixing it with [string]... so [string]$computer and "$computer" are functionally the same thing.

    Now I'm interested in why this behavior is occurring, if I find anything, I'll update this post :), glad you got it working though.
     
  20. Foggywindshield

    Foggywindshield Member

    Joined:
    May 29, 2007
    Messages:
    32
    Location:
    SA
    Hopefully this is of some use to you.

    I had to do something similar the other day when accessing the Office365 REST API and retrieving email attachments that are base64 encoded.

    I only had to save the file using Set-Content but by using what I learnt I have been able to get the content of a zip file using Get-Content and then encode it into base64 before then decoding and saving as a new file.

    The trick I found was that Get-Content accepts an 'encoding' parameter like set-content does but it is not documented from what I could tell.

    When I had to work with the attachment files, the decoded Base64 was in Byte encoding so I have used that again here in my example. It also, is probably the closest representation you will get to straight binary as well.

    Code:
    $Input_File = "test.zip"
    $Output_File = "output.zip"
    
    
    $Original_Content = Get-Content $Input_File -Encoding Byte
    
    $Base64_Content = [System.Convert]::ToBase64String($Original_Content)
    
    $Decoded_Content = [System.Convert]::FromBase64String($Base64_Content)
    
    $Decoded_Content | Set-Content $Output_File -Encoding Byte
     

Share This Page

Advertisement: