Routines.psm1

function Test-AdminPrivilege {
    $windowsIde = [System.Security.Principal.WindowsIdentity]::GetCurrent()
    $windowsUse = New-Object System.Security.Principal.WindowsPrincipal($windowsIde)
    $windowsAdm = [System.Security.Principal.WindowsBuiltInRole]::Administrator

    return $windowsUse.IsInRole($windowsAdm)
}

function Read-Choice {
<#
.SYNOPSIS
    Display a prompt like PowerShell does.
.DESCRIPTION
    Make a Powershell style prompt.
    You must provide the same number of options and help messages.
    The function returns an integer, the selected options.
.EXAMPLE
    PS C:\> Read-Choice -Title 'Do you want to continue ?' -Prompt 'Answer yes or no'
     
    Do you want to continue ?
    Answer yes or no
    [Y] Yes [N] No [?] Help (default is "Y"):
.EXAMPLE
    PS C:\> Read-Choice -Title 'Do you want to continue ?' -Prompt 'Answer yes or no' -Options '&Yes','&No','Yes for &all','N&o for all' -HelpMessage 'Say yes','Say No','Say allways yes','Say allways no'
     
    Do you want to continue ?
    Answer yes or no
    [Y] Yes [N] No [?] Help (default is "Y"):
.LINK
    https://dawan.fr
#>

    [CmdletBinding()]param(
        [parameter(Mandatory)]
        [string]$Title,
        [parameter(Mandatory)]
        [string]$Prompt,
        [string[]]$Options=@('&Yes','&No'),
        [string[]]$HelpMessages=@('','')
    )
    if ($Options.Count -ne $HelpMessages.Count) {
        Throw 'Please provide as much Options as HelpMessages'
    }
    $tabOptions = @()
    $i=0
    foreach ($opt in $Options) {
        $tabOptions += New-Object System.Management.Automation.Host.ChoiceDescription $opt, $HelpMessages[$i]
        $i++
    }
    $Choices = [System.Management.Automation.Host.ChoiceDescription[]] ($tabOptions)
    
    return $host.ui.PromptForChoice($Title,$Prompt,$Choices,0)
}

function Read-PopUp {
<#
.SYNOPSIS
    Display a popup.
.DESCRIPTION
    Provides a wrapper for MessageBox.
    Returns the value of the selected button.
.EXAMPLE
    PS C:\> Read-PopUp -Title Information -Message 'information to display'
    Ok
.EXAMPLE
    PS C:\> Read-PopUp -Title Question -Message 'Continue ?' -Buttons YesNo
    No
.EXAMPLE
    PS C:\> Read-PopUp -Title Question -Message "What to do?" -Buttons AbortRetryIgnore -Icon Error -DefaultButton Button3
    Retry
.LINK
    https://dawan.fr
#>

    [CmdletBinding()]param(
        [parameter(Mandatory)]
        [string]$Title,
        [parameter(Mandatory)]
        [string]$Message,
        [ValidateSet('Ok', 'AbortRetryIgnore','OkCancel','RetryCancel','YesNo','YesNoCancel')]
        [string]$Buttons='Ok',
        [ValidateSet('Asterisk','Error','Exclamation','Hand','Information','None','Question','Stop','Warning')]
        [string]$Icon='None',
        [ValidateSet('Button1','Button2','Button3')]
        [string]$DefaultButton='Button1'
    )
    Add-Type -AssemblyName "System.Windows.Forms"
    $dlg = [System.Windows.Forms.MessageBox]
    $btn = [System.Windows.Forms.MessageBoxButtons]::$Buttons
    $icn = [System.Windows.Forms.MessageBoxIcon]::$Icon
    $dfb = [System.Windows.Forms.MessageBoxDefaultButton]::$DefaultButton
    return $dlg::Show($Message, $Title, $btn, $icn, $dfb)
}
function Remove-Diacritics {
<#
.SYNOPSIS
    Function Designed for remove special characters and provides a string usable for login.
.LINK
    https://dawan.fr
#>

    [CmdletBinding()]param(
        [parameter(Mandatory)]
        [string]$String
    )
    return [Text.Encoding]::ASCII.GetString([Text.Encoding]::GetEncoding("Cyrillic").GetBytes($String))
}

function Clear-String {
<#
.SYNOPSIS
    Function designed for remove special characters and provides a string usable for login.
.LINK
    https://dawan.fr
#>

    param(
        [parameter(Mandatory)]
        [string]$String,
        [string[]]$DeletedChars = @('-',' ',"'"),
        [string[]]$ReplacedChars = @('œ','æ'),
        [string[]]$ReplacedByChars = @('oe','ae'),
        [switch]$LowerCase,
        [ValidateRange(1,100)]
        [byte]$MaxLength = 20
    )
    foreach ($char in $DeletedChars) {
        $String = $String -replace $char,''  # opérateur de remplacement
    }

    $i = 0
    foreach ($char in $ReplacedChars) {
        $String = $String -replace $char,$ReplacedByChars[$i]
        $i++
    }
    if ($String.length -gt $MaxLength) { $String = $String.SubString(0, $MaxLength) }
    if ($LowerCase) { $String = $String.ToLower() }
    return $String
}

function Get-Password {
<#
.SYNOPSIS
    Function returning a password according to complexity required in AD domain.
.DESCRIPTION
    You can change the numbers of letters, digits and non alpha-numeric characters required.
    You can modify the sets of allowed characters.
.LINK
    https://dawan.fr
#>

    param(
        [byte]$NumLetters = 5,
        [byte]$NumDigits = 2,
        [byte]$NumNonAlphaNum = 1,
        [switch]$AsSecureString,
        [string]$LettersAllowed = 'azertyuiopmlkjhgfdsqwxcvbn',
        [string]$DigitsAllowed = '0123456789',
        [string]$NonAlphNumAllowed = ':;,!*$=-'
    )
    $LettersTab = $LettersAllowed -split ''
    $DigitsTab = $DigitsAllowed -split ''
    $NonAlphNumTab = $NonAlphNumAllowed -split ''

    # Tirer au sort dans un tableau : Get-Random
    $CharTab = $LettersTab | Get-Random -Count $NumLetters -ea Ignore  # ea : ErrorAction
    $CharTab += $DigitsTab | Get-Random -Count $NumDigits -ea Ignore
    $CharTab += $NonAlphNumTab | Get-Random -Count $NumNonAlphaNum -ea Ignore

    # Mélanger le tableau : Sort-Objet avec Get-Random
    $CharTab = $CharTab | Sort-Object {Get-Random}
    $Password = $CharTab -Join ''   # opérateur de rassemblement d'élément de tab
    if ($AsSecureString) {
        return (ConvertTo-SecureString $Password -AsPlainText -Force)
    } else {
        return $Password
    }
}

function Convert-IPtoInt32 () { 
    param (
      [parameter(Mandatory=$true)]
      [ipaddress]$ip
    ) 
    $octets = ([string]($ip)).split(".") 
    return [uint32]([uint32]$octets[0] * 16777216 + [uint32]$octets[1] * 65536 + [uint32]$octets[2] * 256 + [uint32]$octets[3])
} 
  
  function Convert-Int32toIP() { 
    param (
      [parameter(Mandatory=$true)]
      [uint32]$int
    ) 
    return (([math]::truncate($int / 16777216)).tostring() + "." + ([math]::truncate(($int % 16777216) / 65536)).tostring() + "." + ([math]::truncate(($int % 65536) / 256)).tostring() + "." + ([math]::truncate($int % 256)).tostring() )
}
  
function Get-IPv4Range {
<#
.SYNOPSIS
    Get the IP addresses in a range
.DESCRIPTION
    Returns an array
.EXAMPLE
    Get-IPrange -start 192.168.8.2 -end 192.168.8.20
.EXAMPLE
    Get-IPrange -IPv4Address 192.168.8.2 -NetMask 255.255.255.0
.EXAMPLE
    Get-IPrange -IPv4Address 192.168.8.3 -CIDRMask 24 -Addressable
.LINK
    https://dawan.fr
#>
 
    [CmdletBinding(DefaultParameterSetName='mask')]
    param ( 
      [Parameter(Mandatory=$true, ParameterSetName='range', Position=0)]
      [ipaddress]$Start, 
      [Parameter(Mandatory=$true, ParameterSetName='range', Position=1)]
      [ipaddress]$End,
  
      [Parameter(Mandatory=$true, ParameterSetName='mask', Position=0)]
      [Parameter(Mandatory=$true, ParameterSetName='cidr', Position=0)]
      [ipaddress]$IPv4Address, 

      [Parameter(ParameterSetName='mask', Position=1)]
      [ipaddress]$NetMask = '255.255.255.0', 
      
      [Parameter(ParameterSetName='cidr', Position=1)]
      [ValidateRange(0,32)]
      [uint32]$CIDRMask = 24,
  
      [Parameter(ParameterSetName='mask', Position=2)]
      [Parameter(ParameterSetName='cidr', Position=2)]
      [switch]$Addressable
    ) 
   
    Switch -Regex ($PSCmdlet.ParameterSetName) {
      'mask|cidr' {
        $ipaddr = [Net.IPAddress]::Parse($IPv4Address)
        if ($PSCmdlet.ParameterSetName -eq 'cidr') { 
          $maskaddr = [Net.IPAddress]::Parse((Convert-Int32toIP -int ([convert]::ToInt64(("1" * $CIDRMask + "0" * (32 - $CIDRMask)), 2)))) 
        } else { 
          $maskaddr = [Net.IPAddress]::Parse($NetMask)
        }
  
        $networkaddr = New-Object net.ipaddress ($maskaddr.address -band $ipaddr.address)
        $broadcastaddr = New-Object net.ipaddress (([system.net.ipaddress]::parse("255.255.255.255").address -bxor $maskaddr.address -bor $networkaddr.address))
      
        $startaddr = Convert-IPtoInt32 -ip $networkaddr.ipaddresstostring 
        $endaddr = Convert-IPtoInt32 -ip $broadcastaddr.ipaddresstostring 
      }
      'range' { 
        $startaddr = Convert-IPtoInt32 -ip $Start 
        $endaddr = Convert-IPtoInt32 -ip $End 
      }
    }
  
    if ($Addressable) {
      $startaddr++
      $endaddr--
    }
  
    for ($i = $startaddr; $i -le $endaddr; $i++) { 
      Convert-Int32toIP -int $i 
    }
}
  
function Get-IPv4Info {
<#
.SYNOPSIS
    Returns information about IPv4 address.
.DESCRIPTION
    Returns
      - class,
      - type : public, private, multicast or reserved
      - start ip and end ip
.LINK
    https://dawan.fr
.EXAMPLE
PS C:\> Get-IPv4Info 192.168.75.2
Class : C
Type : Private
IPStart : 192.168.0.0
IPEnd : 192.168.255.255
IPAddress : 192.168.75.2
.EXAMPLE
PS C:\> Get-IPv4Info 12.45.123.201
Class : A
Type : Public
IPStart : 0.0.0.0
IPEnd : 126.255.255.255
IPAddress : 12.45.123.201
 
#>

    [CmdletBinding()]
    param(
      [parameter(Mandatory=$true)]
      [ipaddress]$IPAddress
    )
    $networks = @(
      [PSCustomObject] @{end=167772159;  Class='A'; Type='Public';    IPStart='0.0.0.0';     IPEnd='126.255.255.255'}, # 0.0.0.0 - 9.255.255.255
      [PSCustomObject] @{end=184549375;  Class='A'; Type='Private';   IPStart='10.0.0.0';    IPEnd='10.255.255.255'},  # 10.0.0.0/8
      [PSCustomObject] @{end=2130706431; Class='A'; Type='Public';    IPStart='0.0.0.0';     IPEnd='126.255.255.255'}, # 11.0.0.0 - 126.255.255.255
      [PSCustomObject] @{end=2147483647; Class='-'; Type='Localhost'; IPStart='127.0.0.0';   IPEnd='127.255.255.255'}, # 127.0.0.0/8
      [PSCustomObject] @{end=2886729727; Class='B'; Type='Public';    IPStart='128.0.0.0';   IPEnd='191.255.255.255'}, # 128.0.0.0 - 172.15.255.255
      [PSCustomObject] @{end=2887778303; Class='B'; Type='Private';   IPStart='172.16.0.0';  IPEnd='172.31.255.255'},  # 172.16.0.0/12
      [PSCustomObject] @{end=3221225471; Class='B'; Type='Public';    IPStart='128.0.0.0';   IPEnd='191.255.255.255'}, # 172.32.0.0 - 191.255.255.255
      [PSCustomObject] @{end=3232235519; Class='C'; Type='Public';    IPStart='192.0.0.0';   IPEnd='223.255.255.255'}, # 192.0.0.0 - 192.167.255.255
      [PSCustomObject] @{end=3232301055; Class='C'; Type='Private';   IPStart='192.168.0.0'; IPEnd='192.168.255.255'}, # 192.168.0.0/16
      [PSCustomObject] @{end=3758096383; Class='C'; Type='Public';    IPStart='192.0.0.0';   IPEnd='223.255.255.255'}, # 192.169.0.0 - 223.255.255.255
      [PSCustomObject] @{end=4026531839; Class='D'; Type='Multicast'; IPStart='224.0.0.0';   IPEnd='239.255.255.255'}, # 224.0.0.0 - 239.255.255.255
      [PSCustomObject] @{end=4294967295; Class='E'; Type='Reserved';  IPStart='240.0.0.0';   IPEnd='255.255.255.255'}  # 240.0.0.0 - 255.255.255.255
    )
    $ip = Convert-IPtoInt32 $IPAddress
    $net = $networks | Where-Object end -ge $ip | Select-Object -First 1 | Select-Object Class,Type,IpStart,IPEnd
    $net | Add-Member -Name IPAddress -Value $IPAddress -MemberType NoteProperty
    return $net
}
  
New-Alias -Name gir -Value Get-IPv4Range

function Test-ICMP {
<#
.SYNOPSIS
    Faster ping
.DESCRIPTION
    Ping with a timeout in milliseconds customizable
.PARAMETER ComputerName
    IP or hostname
.PARAMETER Network
    Network : X.X.X.X/CIDR. eg : 192.168.1.0/24
.LINK
    https://dawan.fr
.EXAMPLE
    Test-MyTestFunction -Verbose
    Explanation of the function or its result. You can include multiple examples with additional .EXAMPLE lines
#>

    [cmdletBinding(DefaultParameterSetName='computer')]
    param(
        [parameter(Mandatory=$true, ParameterSetName='computer', Position=0)]
        [string]$ComputerName,

        [parameter(Mandatory=$true, ParameterSetName='network', Position=0)]
        [ValidateScript({ [boolean]([ipaddress]($_.Split('/')[0])) -and ([byte]($_.Split('/')[1] -in 0..32)) })]
        [string]$Network,

        [parameter(Position=1)]
        [uint32]$Count=4,

        [parameter(Position=2)]
        [ValidateRange(1, 10000)]
        [uint32]$Timeout=200,

        [parameter(Position=3)]
        [ValidateRange(1, 128)]
        [byte]$TTL=128,

        [parameter(Position=4)]
        [ValidateRange(20,980)]
        [uint16]$Length = 32,

        [parameter(ParameterSetName='computer', Position=5)]
        [Switch]$Quiet,

        [parameter(ParameterSetName='network', Position=5)]
        [Switch]$Up
    )

    $result = $false
    if ($Count -eq 0) { $Count = 2GB }
    $ps = New-Object System.Net.NetworkInformation.Ping
    
    $data = "aquickbrownfoxjumpedoverthelazydog"
    $buffer = [System.Text.Encoding]::ASCII.GetBytes($data)

    $po = New-Object System.Net.NetworkInformation.PingOptions 
    $po.DontFragment = $true
    $po.Ttl = $TTL
  
    switch ($PSCmdlet.ParameterSetName) {
        'computer' {
            for ($i = 1; $i -le $Count; $i++) {
                $reply = $ps.Send($ComputerName, $Timeout, $buffer, $po)

                if (-not $Quiet) {
                    Write-Output ([PSCustomObject]@{
                        PSTypeName = 'My.ICMPResult'
                        Number = $i
                        Address = $reply.Address
                        Status = $reply.Status
                        Time = $reply.RoundtripTime
                        TTL = $reply.Options.Ttl
                        Data = $reply.Buffer.Length
                    })
                } else {
                    if ($reply.Status -eq 'Success') { $s = $true } else { $s = $false }
                    $result = $result -or $s
                }
            }
            if ($Quiet) { return $result }
        }
        'network' {
            $target = Get-IPv4Range -IPv4Address ($Network.Split('/')[0]) -CIDRMask ($Network.Split('/')[1]) -Addressable
            foreach($ComputerName in $target) {
                $result = @()
                for ($i = 1; $i -le $Count; $i++) {
                    $reply = $ps.Send($ComputerName, $Timeout, $buffer, $po)
                    $result += ([PSCustomObject]@{
                        PSTypeName = 'My.ICMPResult'
                        Number = $i
                        Address = $reply.Address
                        Status = $reply.Status
                        Time = $reply.RoundtripTime
                        TTL = $reply.Options.Ttl
                        Data = $reply.Buffer.Length
                    })
                }
                    
                $PingSuccess = $result | Where-Object status -eq 'success' | Measure-Object Time -Average -Minimum -Maximum
                $Succeeded = [boolean]($PingSuccess.Count)
                $PercentSucceeded = [int]($PingSuccess.Count * 100 / $Count)
                if (($Up -and $Succeeded) -or (-not $Up)) {
                    Write-Output ([PSCustomObject]@{
                        PSTypeName = 'My.ICMPNetworkResult'
                        IPAddress = $ComputerName
                        Succeeded = $Succeeded
                        PercentSucceeded = $PercentSucceeded
                        AverageTime = [int]($PingSuccess.Average)
                        MinTime = $PingSuccess.Minimum
                        MaxTime = $PingSuccess.Maximum
                        DetailedInfo = $result
                    })
                }
            }
        }
    }
}

function New-HVClone {
    <#
    .SYNOPSIS
        Clone an Hyper-V VM
    .DESCRIPTION
    Based on Powershel Script New-VMClone.ps1 written by Florian Burnel www.it-connect.fr
    Thanks to him
    .EXAMPLE
        New-HVClone -SrcVMName Debian -DstVMName Debian-Clone
    #>

    [cmdletBinding()]
    param(
        [parameter(Mandatory)]
        [ValidateScript({Test-AdminPrivilege -and [Boolean](Get-VM $_)})]
        [string]$SrcVMName,

        [parameter(Mandatory)]
        [ValidateScript({Test-AdminPrivilege -and ((Get-VM $_).Count -eq 0)})]
        [string[]]$DstVMName,

        [Switch]$StartNewVM,
        [Switch]$DisconnectNetworkAdapter,

        [string]$DstFolder = "C:\VM"
    )
    $ErrorActionPreference = 'Stop'

    Write-Verbose "1 : Create destination folder"
    New-Item $DstFolder -ItemType Directory -Force | Out-Null

    foreach ($VMName in $DstVMName) {
        Write-Verbose "2 : rename VM ($SrcVMName -> $VMName)"
        Rename-VM -VMName $SrcVMName -NewName $VMName
        Write-Verbose "3 : export VM ($VMName)"
        Export-VM -Name $VMName -Path $DstFolder -CaptureLiveState CaptureSavedState
        Write-Verbose "4 : rollback VM to old name"
        Rename-VM -VMName $VMName -NewName $SrcVMName

        Write-Verbose "5 : register new VM"
        $FileVMCX = (Get-ChildItem -Path "$DstFolder\$VMName\Virtual Machines" -Filter *.vmcx).FullName
        Import-VM -Path $FileVMCX -Copy:$false -GenerateNewId `
                -VirtualMachinePath "$DstFolder\$VMName\Virtual Machines" `
                -VhdDestinationPath "$DstFolder\$VMName\Virtual Hard Disks"
        
        Write-Verbose "6 : add a note to the VM"
        Set-VM -Name $VMName -AutomaticCheckpointsEnabled $false -Notes "Clone of $SrcVMName"
        if ($DisconnectNetworkAdapter) {
            Write-Verbose "7 : disconnect VM network adapter"
            Disconnect-VMNetworkAdapter -VMName $VMName
        }
        if ($StartNewVM) {
            Write-Verbose "8 : start VM"
            Start-VM -VMName $VMName
        }
    }
}