Powershell script - creation of VM and install OS

Discussion in 'Business & Enterprise Computing' started by Gunna, Mar 16, 2015.

  1. Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    I want to automate the process of creating and installing a VM on a hyper-V server.

    I'm still learning powershell so it has taken me a while to get to this point and I did end up copying someone elses script for the prompting of the values as I couldnt get it working right.

    I have created the script to create the VM:

    So I have copied the server 2012 r2 ISO to the D drive of the hyper-v server and created an unattend.xml but not sure how to script that side of it to test it works.

    Any help?

    Edit: I found this https://gallery.technet.microsoft.com/scriptcenter/Convert-WindowsImageps1-0fe23a8f

    It created a VHDX file from an ISO, testing it with my unattend file to see if it works.
     
    Last edited: Mar 16, 2015
  2. PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,621
    Stop thinking of it as a one-step process.

    First (which your script does), you define and create your VM (think of it as specing and buying hardware) this is separate from the operating system that will go on it.

    Once the VM is created (you have bought your hardware), Then you can perform an unattended installation on it.

    http://www.ragnarharper.com/howto_g...d-installation-of-windows-server-2012-r2.html seems like an OK guide for creating an one of customised iso.

    Once you've got your iso, you can attach it with Set-VMDVDDRive and boot it up. (Start-VM). It should then finish the install and be ready to rock.

    As a learning excersize its great, but would have no real carry-over into real world scenarios. In the real world, you would create and use Templates
     
  3. chewbucca

    chewbucca Member

    Joined:
    Jun 4, 2003
    Messages:
    1,892
    you might be interested in reading up on Powershell DSC
     
  4. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Ok, decided to use a template VHDX

    So I have created a CM that has all windows updates and will be offline.
    I have then copied the VHDX and sysprepped it. This will then be my template VHDX file and it is saved in D:\Administrative\Template VHDX\2012 R2 OS\AUTemplate.VHDX on the hyperv host.

    The new script I have is:
    I have an issue:

    1) If I enter a value in the memory section i get this error:
    If I manually edit the script I can edit the RAM size and it work, if I put 4096MB the VM has 4GB ram. If i type either 4096 or 4096 using the above script it fails.
     
    Last edited: Mar 18, 2015
  5. PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,621
    New-VM is smart
    New-VM understands MB/GB etc

    Read-Host is Dumb
    Read-Host takes whatever you feed it.

    MB and GB are built in powershell constants, that can be used for conversion...

    When you do

    Code:
    New-VM -Name Blah -Path "Blah\Blah" [B]-MemoryStartupBytes 4096MB[/B] -SwitchName Blah -generation 2
    
    What New-VM is doing is

    -MemoryStartupBytes (4096 * 1MB)

    which is

    (4096 * 1048576)

    when you use

    -MemoryStartupBytes 4096

    and omit the MB bit, New-VM tries to set your the memory for you VM as 4KB (4096 BYTES) and then complains, because it is not 1970 anymore.

    So you need to either do the multiplication on the SRAM value yourself, or append MB to it... So Lets do that...

    Step 1... Check what type of object Read-Host is giving us

    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    
    
    $SRAM | Get-Member
    
    ---
    
     TypeName: [B]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[string].CompareTo(string other)       
    <SNIP
    IConvertible.ToUInt64(System.IFormatProvider provider)                                                                                                       
    ToUpper          Method                string ToUpper(), string ToUpper(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)                                                                                                                           
    Chars            ParameterizedProperty char Chars(int index) {get;}                                                                                                                                        
    Length           Property              int Length {get;}                              
    
    Allrighty, its a string so we can check if its ends in "MB" and if it doesn't we can add it

    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    
    
    if ($SRAM -notlike "*MB") {$SRAM += "MB"}
    
    Write-Host $SRAM
    
    ---
    
    4096MB
    
    Depending on your method of launching this script, and its target audience, you may want to put in more error and bounds checking, so people don't go creating VM's with stupid amounts of resources..

    Basically, if you add

    Code:
    if ($SRAM -notlike "*MB") {$SRAM += "MB"}
    
    after you have set the $SRAM value, then New-VM is more likely to like it.
     
  6. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Wow! Thanks. Still learning powershell actually so handy to know the above, got my "Powershell in 30 lunches" book.

    I'll be the one using the script, generally it will be run fromt he HYperV server via a remote PS session. Once it becomes time for others to use it I will enter a condition that sets the maximum allowed ram.

    Intersting, I entered that line and receive the following:

    If I copy
    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    
    
    if ($SRAM -notlike "*MB") {$SRAM += "MB"}
    
    Write-Host $SRAM
    to a seperate PS1 script i get an output as well but combine it in this script and it fails:

     
    Last edited: Mar 18, 2015
  7. PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,621
    That will teach me for not testing... But it does illustrate the problem of loosely typed languages :)

    $SRAM is of type system.string
    -MemoryStartupBytes parameter wants to be fed 64bit Integer (int64)

    Read-Host sets the type to whatever it feels best fits, but once we manipulate it, it becomes a stystem.string, which New-VM bitches about... so if we roll with


    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    
    if ($SRAM -notlike "*MB") {[int64]$SRAM = $SRAM * 1MB}
    
    write-host $SRAM
    We should get our $SRAM variable, in a format that New-VM is happy to consume.

    We do this by prefixing the $SRAM variable with [int64] to make powershell always treat it as a 64 bit integer.

    We cant have alphanumerics in our Variable, and powershell seems a bit variable about its handling of constants and typing of data, so we keep it nice and easy.

    If our $SRAM variable dosen't have MB on the end of it, then we Turn it into a 64 bit integer by multiplying it by 1MB

    If our $SRAM variable does end in MB, we leave it alone, and I think New-VM should be happy (because we haven't played with it, it can still expand it out to be <value * 1MB>)
     
  8. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Holy smokes, it hates that line!!

    If I enter 2048 as memory value a get a flood of 2048 in red that fills the PS window. I'm thinking it may be a case of having to edit the script for the value I want.

    Apart from Read-Host is there another command I can use that will prompt for values?

    Edit:

    Ok, Playing around with that arguement, if I get it I will post back but it seems to have trouble converting it to the format New-VM likes
     
    Last edited: Mar 18, 2015
  9. PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,621
    If its just you that is entering the inputs, and you know not to put MB on the end, then you can just remove the check for MB, and use

    Code:
    [int64]$SRAM = ($SRAM * 1MB)
    to convert your value, from MB to Bytes, and feed that in

    or you can wrap read-host in a try/catch block, to ensure that only numbers are fed in.

    Code:
    do
    {
      try
      {
        [int64]$SRAM = Read-Host Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
        $inputOK = $true
      }
      catch
      {
        Write-Host -ForegroundColor red "INVALID INPUT!  Please enter a numeric value."
      } 
    }
    until ($inputOK)
    $SRAM = ($SRAM * 1MB)
    
    Write-Host $SRAM
    
     
  10. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,090
    Location:
    NSW Central Coast
    Here's how I approached the same problem...

    Start with a template VHDX (built the way you want then sysprep'd). This gets you patch currency and the "build" process is:
    • Boot
    • Enter new password

    I build a list of VMs to create (CSV format) - the header is here with two example VMs (MinRAM=0 means fixed memory defined by StartRAM, otherwise it's dynamic). It's intended to solve most problems so it assumes max 2 NICs and max 3 disks (OS + 2 x Data) - anything more and you add it yourself later :) :

    Code:
    Name,CPUs,MinRAM,StartRAM,MaxRAM,vLAN1,vSwitch1,vLAN2,vSwitch2,Disk1GB,Disk2GB
    DC01,2,500,1000,2000,110,vSwitch01,,,,
    ExchMbx01,4,0,8000,0,110,vSwitch01,1310,vSwitch01,200,40
    
    This is BuildVMs.ps1. You might get some useful stuff from it; feel free to use or ignore as you prefer.

    Code:
    [CmdletBinding(SupportsShouldProcess=$True)]
    Param()
    
    $OldPref = $DebugPreference
    if ($PSCmdlet.MyInvocation.BoundParameters["Debug"].IsPresent) { $DebugPreference = "Continue" }
    
    
    $VMs = Import-CSV ".\VMList.txt"
    $CPUs = 0
    $MinRAM = 0
    $StartRAM = 0
    $MaxRAM = 0
    $OverheadRAM = 0
    $Disk = 0
    $VMRootPath = "C:\ClusterStorage\Volume2"
    $OSSourceDisk = "C:\VMTemplate\Win12R2-Template.vhdx"
    
    if ( -not $VMRootPath.EndsWith("\") ) { $VMRootPath += "\" }
    Write-Debug "Processing $($VMs.Count) Virtual Machines"
    
    foreach ($VM in $VMs) {
    
      Write-Debug "Processing $($VM.Name)"
      
      $VMPath = $VMRootPath + $VM.Name
      mkdir $VMPath | Out-Null
      
      New-VM -Name $VM.Name -Gen 2 -NoVHD -Path $VMRootPath | Enable-VMIntegrationService -name "Guest Service Interface"
      if ($PSCmdlet.ShouldProcess($VM.Name, "Update number of CPUs to $($VM.CPUs)")) { Set-VM -Name $VM.Name -ProcessorCount $VM.CPUs }
    
      Write-Debug $("{0} has {1} CPUs assigned" -f $VM.Name, $VM.CPUs)
      $CPUs += $VM.CPUs
      if ($VM.MinRAM -eq 0) {
        Write-Debug $("{0} will be configured for {1} MB static RAM" -f $VM.Name, $VM.StartRAM)
        $MinRAM += $VM.StartRAM
        $StartRAM += $VM.StartRAM
        $MaxRAM += $VM.StartRAM
        $OverheadRAM += 32 + [decimal]::Ceiling(8*$VM.StartRAM / 1000)
        if ($PSCmdlet.ShouldProcess($VM.Name, "Configure VM for static memory")) { Set-VM -Name $VM.Name -StaticMemory -MemoryStartupBytes (1MB * [Int]::Parse($VM.StartRAM)) }
      } else {
        Write-Debug $("{0} will be configured for {1} MB/{2} MB/{3} MB dynamic RAM" -f $VM.Name, $VM.MinRAM, $VM.StartRAM, $VM.MaxRAM)
        $MinRAM += $VM.MinRAM
        $StartRAM += $VM.StartRAM
        $MaxRAM += $VM.MaxRAM
        $OverheadRAM += 32 + [decimal]::Ceiling(8*$VM.MaxRAM / 1000)
        if ($PSCmdlet.ShouldProcess($VM.Name, "Configure VM for dynamic memory")) { Set-VM -Name $VM.Name -DynamicMemory -MemoryStartupBytes (1MB * [Int]::Parse($VM.StartRAM)) -MemoryMinimumBytes (1MB * [Int]::Parse($VM.MinRAM)) -MemoryMaximumBytes (1MB * [Int]::Parse($VM.MaxRAM)) }
      }
    
      $Disk += 64 + $VM.Disk1GB + $VM.Disk2GB
    
      if ($PSCmdlet.ShouldProcess($VM.Name, "Add DVD Drive")) { Add-VMDvdDrive -VMName $VM.Name -ControllerNumber 0 -ControllerLocation 63 }
      Copy $OSSourceDisk "$VMPath\$($VM.Name)-Disk0.VHDX"
      if ($PSCmdlet.ShouldProcess($VM.Name, "Add OS Disk Drive")) { Add-VMHardDiskDrive -VMName $VM.Name -ControllerNumber 0 -ControllerType SCSI -ControllerLocation 0 -Path "$VMPath\$($VM.Name)-Disk0.VHDX" }
    
      if (($VM.Disk1GB -ne 0) -and -not ([String]::IsNullOrWhiteSpace($VM.Disk1GB))) {
        New-VHD -Path "$VMPath\$($VM.Name)-Disk1.VHDX" -SizeBytes (1GB * [Int]::Parse($VM.Disk1GB)) -Dynamic
        if ($PSCmdlet.ShouldProcess($VM.Name, "Add Data Disk Drive")) { Add-VMHardDiskDrive -VMName $VM.Name -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 1 -Path "$VMPath\$($VM.Name)-Disk1.VHDX" }
      }
      if (($VM.Disk2GB -ne 0) -and -not ([String]::IsNullOrWhiteSpace($VM.Disk2GB))) {
        New-VHD -Path "$VMPath\$($VM.Name)-Disk2.VHDX" -SizeBytes (1GB * [Int]::Parse($VM.Disk2GB)) -Dynamic
        if ($PSCmdlet.ShouldProcess($VM.Name, "Add Data Disk Drive")) { Add-VMHardDiskDrive -VMName $VM.Name -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 2 -Path "$VMPath\$($VM.Name)-Disk2.VHDX" }
      }
    
      if ($PSCmdlet.ShouldProcess($VM.Name, "Remove pre-configured Network Adapters")) { Remove-VMNetworkAdapter -VMName $VM.Name }
    
      if (-not ([String]::IsNullOrWhiteSpace($VM.vLAN1))) {
        if (($VM.vLAN1 -ne 0) -and ($VM.vLAN1 -ne 1)) {
          if ($PSCmdlet.ShouldProcess($VM.Name, "Add Network Adapter")) { Add-VMNetworkAdapter -VMName $VM.Name -SwitchName $VM.vSwitch1 -Passthru | Set-VMNetworkAdapterVlan -Access -VlanId $VM.vLAN1 }
        } else {
          if ($PSCmdlet.ShouldProcess($VM.Name, "Add Network Adapter")) { Add-VMNetworkAdapter -VMName $VM.Name -SwitchName $VM.vSwitch1 -Passthru | Set-VMNetworkAdapterVlan -Access -Untagged }
        }
      }
    
      if (-not ([String]::IsNullOrWhiteSpace($VM.vLAN2))) {
        if (($VM.vLAN2 -ne 0) -and ($VM.vLAN2 -ne 1)) {
          if ($PSCmdlet.ShouldProcess($VM.Name, "Add Network Adapter")) { Add-VMNetworkAdapter -VMName $VM.Name -SwitchName $VM.vSwitch2 -Passthru | Set-VMNetworkAdapterVlan -Access -VlanId $VM.vLAN2 }
        } else {
          if ($PSCmdlet.ShouldProcess($VM.Name, "Add Network Adapter")) { Add-VMNetworkAdapter -VMName $VM.Name -SwitchName $VM.vSwitch2 -Passthru | Set-VMNetworkAdapterVlan -Access -Untagged }
        }
      }
    
      if ($PSCmdlet.ShouldProcess($VM.Name, "Complete configuration")) { 
        if ($VM.MinRAM -eq 0) {
          Write-Output $("Configured VM {0}, with {1} vCPUs, {2} GB static RAM." -f $VM.Name, $VM.CPUs, ($VM.StartRAM/1000))
        } else {
          Write-Output $("Configured VM {0}, with {1} vCPUs, {2}/{3}/{4} GB dynamic RAM." -f $VM.Name, $VM.CPUs, ($VM.MinRAM/1000), ($VM.StartRAM/1000), ($VM.MaxRAM/1000))
        }
        Write-Output "VM Disks:"
        foreach ($vDisk in (Get-VM $VM.Name | Get-VMHardDiskDrive)) {
          Write-Output $("{0}/{1:D2}/{2:D2} {3}" -f $vDisk.ControllerType, $vDisk.ControllerNumber, $vDisk.ControllerLocation, $vDisk.Path)
        }
        foreach ($vDisk in (Get-VM $VM.Name | Get-VMDvdDrive)) {
          Write-Output $("{0}/{1:D2}/{2:D2} {3}" -f $vDisk.ControllerType, $vDisk.ControllerNumber, $vDisk.ControllerLocation, "DVD Drive")
        }
        Write-Output "VM Networks:"
        foreach ($vNic in (Get-VM $VM.Name | Get-VMNetworkAdapter)) {
          Write-Output $("{0} {1} {2}" -f $vNic.Name.PadRight(30, " "), $vNic.SwitchName.PadRight(30, " "), (Get-VMNetworkAdapterVlan -VMNetworkAdapter $vNic).AccessVlanId)
        }
      }
    }
    
    Write-Output $("Configure {0} VMs, with {1} vCPUs, {2}/{3}/{4} GB RAM ({5} MB overhead + 2 GB for the host), and {6} GB Disk" -f $VMs.Count, $CPUs, ($MinRAM/1000), ($StartRAM/1000), ($MaxRAM/1000), $OverheadRAM, $Disk)
    
     
  11. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Hi,

    Cheers for the help. I did try to remove the MB chedck and it comes back with page and pages of the value I enter for memory and then at the end it says:

    E.G I put 512 as ram value: after a good 5 seconds of scrolling through of 512512512 it says:

    I even tried using system.Convert to see if forcing the output out via 'ToInt64' made a difference but it did not.
     
    Last edited: Mar 18, 2015
  12. PabloEscobar

    PabloEscobar Member

    Joined:
    Jan 28, 2008
    Messages:
    14,621
    Script for Building labs for training?
     
  13. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,090
    Location:
    NSW Central Coast
    I'm not sure what the question means. If I were planning to build a stack of Lab/Training VMs that would be a set of VM templates (one per role) - all sysprepped and ready for application of the script above. You could, for example, change the script so it expects the name of the CSV and/or template as a parameter, so you can run BuildVMs.ps1 DCList.txt and BuildVMs.ps1 SQLList.txt. Or you can change the CSV format - if you add a new column Template to the header, Template automatically becomes a new property of the objects you read from the file (each $VM will have a Template property).

    Or you could use a single template then mount the target VHDX image after copying, and copy your role source files. You could also copy/move in a customised answer file to do server naming and Administrator password, have it boot and auto-configure, then use PowerShell DSC for installing roles, features, components, joining the domain etc.

    But the original Q was about getting a VM with an installed OS - building a fully fledged lab takes a little longer :)

    Gunna - can you repost your current code?
     
    Last edited: Mar 19, 2015
  14. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Code:
    # This script creates a new Hyper-V machine with hard drive, memory & network resources configured.
    
    Echo "Variables"
    $SVR1 = Read-Host "Enter the Virtual Machine name (Press [Enter] to choose Server01): "
    if ($SVR1 -eq ""){$SVR1="Server01"} ; if ($SVR1 -eq $NULL){$SRV1="Server01"}
    
    $VMLOC = "d:\virtual machines\config files"
    
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    if ( -not $SRAM.EndsWith("MB") ) { $SRAM += "MB" }
    
    $Network1 = "VmTeamSwitch"
    
    $Processor = Read-Host "Enter the number of processors (Press [Enter] to choose 2): "
    if ($Processor -eq ""){$Processor="2"} ; if ($Processor -eq $NULL){$Processor="2"}
    
    $VHDPath = "D:\Virtual Machines\VHD"
    
    $VM_ROOT_VHD = "$VHDPath\$SVR1\${SVR1}_OS_C_Drive.VHDX"
    
    $VHDTemplate = "D:\Administrative\Template VHDX\2012 R2 OS\AUTemplate.VHDX"
    
    
    Echo "Create VM Folder"
    MD $VMLOC -ErrorAction SilentlyContinue
    MD $VHDPath\$SVR1 -ErrorAction SilentlyContinue
    
    Echo "Create Virtual Machine"
    New-VM -Name $SVR1 -Path $VMLOC -MemoryStartupBytes ${SRAM}MB -SwitchName $Network1 -generation 2
    
    Echo "Copy Template VHDX and rename"
    Convert-VHD -path $VHDTemplate -DestinationPath $VM_ROOT_VHD
    Add-VMHardDiskDrive -VMName $SVR1 -ControllerType SCSI -ControllerNumber 0 -ControllerLocation 0 -Path $VM_ROOT_VHD
    
    Echo "Set number of processors"
    SET-VMProcessor –VMName $SVR1 –Count $Processor
    
    Echo "Set OS VHD as 1st boot order"
    $vhd = Get-VMHardDiskDrive -vmname $SVR1
    Set-VMFirmware -VMName $SVR1 -FirstBootDevice $VHD
    
    
    
    
    I did some more research following on from PabloEscobar's advice. The output from Read-Host is a different class of data. This means that INT64 cannot understand the data being input. Attempting to convert the data doesnt appear to work either.

    I'll have a look at DaidRa's code to see what I can use. At this stage my VM builds will be minimal so editting the above script to include ram will be ok.

    i've decided to use a sysprepped VHDX file and convert it via the script.

    Cheers for the help
     
  15. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,090
    Location:
    NSW Central Coast
    Change this:
    Code:
    if ( -not $SRAM.EndsWith("MB") ) { $SRAM += "MB" }
    To this:
    Code:
    if ( -not $SRAM.EndsWith("MB") ) { $SRAM = 1MB * ([int64]$SRAM) }
    The [int64] before $SRAM instructs PS to interpret the value of $SRAM as a long integer (a 64-bit whole number). The round brackets force that to happen before the multiplication by 1MB, so you are going to get 2 numbers multiplied and stored in $SRAM.

    The original code kept $SRAM as a string, which is not what the other PS cmdlets are expecting.

    If you multiply a string S by a number N, what's produced is a string containing N copies of S - hence the "512512512" you were getting

    Also, style-wise, I prefer to use this to check the value:
    Code:
    if ([String]::IsNullOrEmpty($SRAM)) { $SRAM = 1024MB }
    I find it easier to understand the logic behind what it's doing when I read it 12 months later.

    Ugh. Actually, that's not going to work (the first edit). Once you set $SRAM to 1024MB (null entry to Read-Host), .EndsWith won't exist on $SRAM because it won't be a string object. So here's the original code:
    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ($SRAM -eq ""){$SRAM=1024MB} ; if ($SRAM -eq $NULL){$SRAM=1024MB}
    if ( -not $SRAM.EndsWith("MB") ) { $SRAM += "MB" }
    Make it:
    Code:
    $SRAM = Read-Host "Enter the size of the Virtual Machine Memory (Press [Enter] to choose 1024MB): "
    if ([String]::IsNullOrEmpty($SRAM)) { $SRAM = 1024MB } else { if ( -not $SRAM.EndsWith("MB") ) { $SRAM = 1MB * ([int64]$SRAM) } }
    I think the only case it doesn't handle now is where the user enters something like 4GB or 800MB.


    OK screw all that, here's the RIGHT code:
    Code:
    $SRAM=Read-Host "Enter RAM amount (default: 1024MB)"
    if ([String]::IsNullOrWhiteSpace($SRAM)) { $SRAM="1024MB" }
    if ( ($SRAM.EndsWith "KB") -or  ($SRAM.EndsWith "MB") -or  ($SRAM.EndsWith "GB") ) { 
        $Unit = $SRAM.Substring($SRAM.Length-2)
        switch ($Unit) {
            "KB" { $RAM = 1KB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
            "MB" { $RAM = 1MB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
            "GB" { $RAM = 1GB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
        }
    } else { $RAM = 1GB * [int64]$SRAM } 
    It's still breakable but it takes someone to be deliberately dumb.
     
    Last edited: Mar 19, 2015
  16. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane
    Cheers, Just tried it and it returned the below result:


     
  17. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,090
    Location:
    NSW Central Coast
    Check out the revised revised new version of the revised code that I posted above, which supersedes what you originally saw because it was updated 15m after your post. And if THAT breaks too can you tell me what you entered :) ?
     
    Last edited: Mar 19, 2015
  18. OP
    OP
    Gunna

    Gunna Member

    Joined:
    Dec 25, 2001
    Messages:
    7,899
    Location:
    Brisbane

    Cheers mate. Doesnt seem like an easy thing to achieve and i'm glad I asked as I wouldn't have figured it out at all.

    Used your revised, revivised code of:
    Code:
    $SRAM=Read-Host "Enter RAM amount (default: 1024MB)"
    if ([String]::IsNullOrWhiteSpace($SRAM)) { $SRAM="1024MB" }
    if ( ($SRAM.EndsWith "KB") -or  ($SRAM.EndsWith "MB") -or  ($SRAM.EndsWith "GB") ) { 
        $Unit = $SRAM.Substring($SRAM.Length-2)
        switch ($Unit) {
            "KB" { $RAM = 1KB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
            "MB" { $RAM = 1MB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
            "GB" { $RAM = 1GB * [int64]$SRAM.Substring(0,$SRAM.Length-2) }
        }
    } else { $RAM = 1GB * [int64]$SRAM }

     
  19. freaky_beeky

    freaky_beeky Member

    Joined:
    Dec 2, 2004
    Messages:
    1,169
    Location:
    Brisbane
    Does this work for you?

    Code:
    $message = "Enter RAM amount in GB (or specify unit)"
    do
    {
        [int64]$RAM=$null
        [string]$SRAM=Read-Host $message
        switch -regex ($SRAM)
        {
            '^\d+KB$' { $RAM = 1KB * $SRAM.Substring(0,$SRAM.Length-2) }
            '^\d+MB$' {$RAM = 1MB * $SRAM.Substring(0,$SRAM.Length-2) }
            '^\d+GB$' { $RAM = 1GB * $SRAM.Substring(0,$SRAM.Length-2) }
            '\D+' {Write-Verbose 'No valid integer entered'} #no number means $null 
            '^\d+$' {$RAM = 1GB * $SRAM}
            default {$RAM = 1 * 1GB} #no entry = 1GB
        }
        $message = "Invalid Entry, please enter RAM amount in GB (or specify unit)"
    }
    until ($RAM) 
    
    $RAM
    
    I just converted everything to use regex instead of string manipulation and used Pablo's Do Until loop (and cleaned up the resulting superfluous variables).

    This *should* be fool proof... that said, I've heard rumours that there is always a better fool.

    If you're interested in the regex, head to somewhere like regexr and paste in the pattern e.g. \d+[KMG]B|\d+ and replace the example text with what you're trying to match, eg
    Code:
    2048
    2048KB
    2048MB
    2048GB
    
    Let me know,
    Alan

    EDIT: forgot your preference for default of 1024MB
     
    Last edited: Mar 23, 2015
  20. DavidRa

    DavidRa Member

    Joined:
    Jun 8, 2002
    Messages:
    3,090
    Location:
    NSW Central Coast
    Ooh that's nifty, I'm adding that to the personal grab bag of code snippets. Probably worth checking for lower case (that's the failure mode I see in both codebases) - mine was also broken further because I pasted the wrong version.

    $String = "36KB"

    $String.EndsWith("KB")
    returns True

    $String.EndsWith "KB"
    is a muppet bashing his head on the keyboard and praying for a result.
     
    Last edited: Mar 23, 2015

Share This Page

Advertisement: