Questo è un template base da cui partire per creare script PowerShell con il supporto per il log su file.
PowerShell: Template-SgartExample.ps1
<#
.SYNOPSIS
Template di base per script PowerShell

.DESCRIPTION
Questo template è da usare come base per costruire nuovi script
L'esempio contiene una panoramica delle funzionalità più usate

Per iniziare cancellare tutto quello che c'è nel try dopo il commento MAIN PROGRAM.

Per visualizzare questo help:  Get-Help .\Template-SgartExample.ps1 -Full

#.PARAMETER stacktrace
# volendo posso usare ".PARAMETER" per descrivere i parametri anzichè inserire
# il commento direttamente sul parametro

.EXAMPLE
Template-SgartExample "valore"

.EXAMPLE
Template-SgartExample "valore" -EnableLog
#>

# esempio di installazione modulo: Install-Module -Name PnP.PowerShell

[CmdletBinding()]
param (
    # parametro obbligatorio
    # si può passare dalla pipeline es.: "324" | .\Template-SgartExample.ps1 -Livello medium 
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [int]$NecessarioIntero,

    # parametro con valore di default
    [string]$ComputerName = $env:COMPUTERNAME,  

    # parametro che accetta solo valori specifici
    [ValidateSet("Low", "Medium", "High")]
    [Alias("Livello")]
    [string]$ParamLivello,

    # parametro di tipo flag true/false es.: .\Template-SgartExample.ps1 112233 -EnableLog
    [switch]$EnableLog
)

# ferma l'esecuzione al primo errore
$ErrorActionPreference = "Stop"

$ScriptNameNoExtension = $null
$settingsFileName = $null
$settings = $null

# -------------------------------------------------------------------------------------------
# funzione di log
function Write-ToLog {
    Param (
        [parameter(Mandatory = $true, HelpMessage = "Message string")]
        [ValidateNotNullOrEmpty()]
        [string] $message,

        [parameter(HelpMessage = "Foreground color message")]
        [AllowEmptyString()]
        [string] $ForegroundColor = 'white',

        [parameter(HelpMessage = "Do not write on the console")]
        [switch]$NoHost = $false,

        [parameter(HelpMessage = "Do not write on the file log")]
        [switch]$NoFile = $false
    )
    $m = "$(get-date -Format "yyyy-MM-dd HH:mm:ss") $message"

    if ($null -eq $WriteToLogFullFileName) {
        Set-ToLog
    }
    if ($NoFile -eq $false) {
        $m >> $WriteToLogFullFileName
    }
    if ($NoHost -eq $false) {
        Write-Host $m -ForegroundColor $foregroundColor
    }
}

function Get-FileNameWithDate {
    param (
        [Parameter(Mandatory = $true, HelpMessage = "Prefix for file name")]
        [string] $filePrefix,

        [parameter(HelpMessage = "Period for create a new file")]
        [ValidateSet("Montly", "Daily", "Hourly", "Time", "Unique", "UniqueWithTime")]
        [string] $period = "Daily",

        [parameter(HelpMessage = "File extension")]
        [string] $extension = "txt"
    )


    if ($period -eq "Hourly") {
        $dtString = (get-date -Format "yyyyMMdd-HH");
    }
    elseif ($period -eq "Montly") {
        $dtString = (get-date -Format "yyyyMM");
    }
    elseif ($period -eq "Time") {
        $dtString = (get-date -Format FileDateTime);
    }
    elseif ($period -eq "Unique") {
        $dtString = "$([System.Guid]::NewGuid().toString("N"))";
    }   
    elseif ($period -eq "UniqueWithTime") {
        $dtString = "$(get-date -Format "yyyyMMdd-HHmmss")-$([System.Guid]::NewGuid().toString("N"))";
    }   
    else {
        $dtString = (get-date -Format FileDate); # yyyyMMdd
    }

    # return value
    "$($filePrefix)_$dtString.$extension"
}

function Set-ToLog {
    Param (
        [parameter(HelpMessage = "Enter the prefix of file name")]
        [string] $filePrefix = "sgart",

        [parameter(HelpMessage = "Enter the path of folder logs")]
        [string] $path = "$pwd\logs",

        [parameter(HelpMessage = "Period for create a new file")]
        [ValidateSet("Montly", "Daily", "Hourly", "Time", "Unique", "UniqueWithTime")]
        [string] $period = "Daily"
    )


    if ((Test-Path $path) -eq $false) {
        New-Item -ItemType "directory" -Path $path
    }

    $fileName = Get-FileNameWithDate -filePrefix $filePrefix -Period $period -Extension "log"
    Set-Variable -Name WriteToLogFullFileName -Value "$path\$fileName" -Scope Script -Visibility public
}


# -------------------------------------------------------------------------------------------
# Esempio di funzione che ritorna oggetti (per aggiungere dei parametri vedi esempio Write-ToLog)
# N.B. le funzioni DEVONO essere definite prima di essere richiamate
function Get-ServiceRunning {
    # leggo i servizi filtrando per quelli in running ("$_" rappresenta il valore corrente)
    # con "|" (pipe) passo i valori di uscita (Get-Service) in ingresso al comando successivo (Where-Object)
    $services = Get-Service | Where-Object { $_.Status -eq "Running" }

    # ritorno il valore
    $services 
}

try {

    # -------------------------------------------------------------------------------------------
    # inizializzazionew prima di usare i parametri o il log
    # ricavo il file settings .json partendo sempre dal percorso del file ps1
    $ScriptNameNoExtension = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
    $settingsFileName = "$PSScriptRoot\$scriptNameNoExtension.json"
    # leggo il file (Get-Content) e lo converto in un oggetto json (ConvertFrom-Json)
    $settings = Get-Content $settingsFileName | ConvertFrom-Json

    # inizializzo il LOG per scrivere sempre nella cartella dove risiede lo script in una sottocartellla 'logs'
    Set-ToLog $ScriptNameNoExtension "$PSScriptRoot\logs" -Period Hourly
    
    # -------------------------------------------------------------------------------------------
    # MAIN PROGRAM
    # -------------------------------------------------------------------------------------------

    # esempio di scrittura nel log e su console con Write-ToLog
    Write-ToLog "Start"

    $now = Get-Date

    Write-Host "Now: $now" -ForegroundColor Green


    Write-Host "-Parameters----------------------------------------------------------" -ForegroundColor Yellow

    Write-Host "ParametroObbligatorio: $NecessarioIntero"
    Write-Host "ComputerName: $ComputerName"
    Write-Host "Level: $ParamLivello"
    Write-Host "Enable log: $EnableLog"
    
    Write-Host "`n-`$MyInvocation------------------------------------------------------" -ForegroundColor Yellow

    $MyInvocation

    Write-Host "`n-Runtime parameters--------------------------------------------------" -ForegroundColor Yellow

    Write-Host "`$PsCmdlet.Host.Version: $($PsCmdlet.Host.Version.ToString())"
    Write-Host "`$PsCmdlet.CommandRuntime: $($PsCmdlet.CommandRuntime)"
    Write-Host "`$MyInvocation.InvocationName: $($MyInvocation.InvocationName)"
    Write-Host "`$MyInvocation.MyCommand: $($MyInvocation.MyCommand)"
    Write-Host "`$MyInvocation.MyCommand.Definition: $MyInvocation.MyCommand.Definition (=`$PSCommandPath)"
    Write-Host "`$Pwd: $($Pwd)"

    Write-Host "`$PSScriptRoot: $PSScriptRoot"
    Write-Host "`$PSCommandPath: $PSCommandPath"
    Write-Host "`$ScriptNameNoExtension: $ScriptNameNoExtension"

    Write-Host "`$PShome: $PShome"

    Write-Host "`$PSCulture: $PSCulture"


    Write-Host "`n-Example of Write-XXX------------------------------------------------" -ForegroundColor Yellow

    Write-Host "Example of Write-Host with -ForegroundColor" -ForegroundColor Green
    Write-Warning "Example of Write-Warning"

    Write-Host "`n-Show settings, file .json-------------------------------------------" -ForegroundColor Yellow
    
    Write-Host "`config .json file: $settingsFileName"
    Write-Host "`$varJson.param1: $($settings.param1)"

    Write-Host "`n-Array @(...)--------------------------------------------------------" -ForegroundColor Yellow
    $arr = @("v1", "c2", "h5", "f3")
    $arr += "r9"    # aggiungo un elemento
    $m = $arr.Length
    $i = 0
    $arr | ForEach-Object {
        $item = $_
        $i++
        Write-Host "Item $i di $m valore: $Item"
    }  

    # se voglio stampare numeri in un "locale" specifico
    Write-Host "`n-Formatting number and date------------------------------------------" -ForegroundColor Yellow

    $num = 123456.789
    Write-Host "Show a number (no formatting): $num"

    $ciIT = new-object System.Globalization.CultureInfo "it-IT"
    Write-Host "Show a number (it-IT): $($num.ToString($ciIT))"
    Write-Host "Show a date (it-IT): $($now.ToString($ciIT))"

    $ciEN = new-object System.Globalization.CultureInfo "en-US"
    Write-Host "Show a number (en-US): $($num.ToString($ciEN))"
    Write-Host "Show a date (en-US): $($now.ToString($ciEN))"
    
    Write-Host "`n-Reading objects-----------------------------------------------------" -ForegroundColor Yellow

    # richiamo la funzione prendo solo i primi 10 risultati
    $services = Get-ServiceRunning | Select-Object -First 10

    # visualizzo il risultato nella console
    $services

    Write-Host "`n-Show only some fields with 'select' command and formatting as a table" -ForegroundColor Yellow

    # Select-Object = select
    $services | Select-Object Name | Format-Table

    Write-Host "`n-ForEach scrive su log" -ForegroundColor Yellow

    $services | ForEach-Object {
        # $_ = $PSItem
        Write-ToLog $PSItem.name
    }

    Write-Host "`n-Calculated fileds (Name and Expression)" -ForegroundColor Yellow
    # posso creare dei campi calcolati
    $services | Select-Object Status, @{Name = "Mio campo calcolato"; Expression = { "Nome: $($_.Name) ($($_.DisplayName))" } } | Format-Table

    Write-Host "`Export CSV---------------------------------------------------------" -ForegroundColor Yellow
    $csvPath = "$PSScriptRoot\exports"
    if ((Test-Path $csvPath) -eq $false) {
        New-Item -ItemType "directory" -Path $csvPath
    }
    # creo un nome file univoco
    $csvFileName = "$csvPath\$(Get-FileNameWithDate "csv-export" "Time" "csv")"
    Write-Host "File csv: $csvFileName"
    $services | Export-Csv -Path $csvFileName -Delimiter ";" -NoTypeInformation -Encoding UTF8

}
catch {
    Write-Error "Error: $($_.ToString())" 
    # in alternativa a $_ usare $Error

    # $errormsg = $_.ToString()
    # $exception = $_.Exception
    # $stacktrace = $_.ScriptStackTrace
    # $failingline = $_.InvocationInfo.Line
    # $positionmsg = $_.InvocationInfo.PositionMessage
    # $pscommandpath = $_.InvocationInfo.PSCommandPath
    # $failinglinenumber = $_.InvocationInfo.ScriptLineNumber
    # $scriptname = $_.InvocationInfo.ScriptName
}
finally {
    # questo lo esegue sempre
    Write-ToLog "End"
}
Nello script ci sono vari esempi tra cui:
  • Aggiunta di un Help
  • Aggiunta e gestione parametri
  • Funzioni per il LOG
  • Lettura impostazioni da file JSON
  • Creazione di funzioni
  • MAIN PROGRAM

Aggiunta di un HELP

Al'inizio dello script può essere inserito l'help racchiuso tra i tag <# ... #>
PowerShell: Testo di help
<#
.SYNOPSIS
Template di base per script PowerShell

.DESCRIPTION
Questo template è da usare come base per costruire nuovi script
L'esempio contiene una panoramica delle funzionalità più usate

Per iniziare cancellare tutto quello che c'è nel try dopo il commento MAIN PROGRAM.

Per visualizzare questo help:  Get-Help .\Template-SgartExample.ps1 -Full

#.PARAMETER stacktrace
# volendo posso usare ".PARAMETER" per descrivere i parametri anzichè inserire
# il commento direttamente sul parametro

.EXAMPLE
Template-SgartExample "valore"

.EXAMPLE
Template-SgartExample "valore" -EnableLog
#>
...
e può essere visualizzato tramite il comando
PowerShell
Get-Help .\Template-SgartExample.ps1 -Full

Aggiunta e gestione parametri

Al PowerShell possono essere passati dei parametri che vanno definiti con l'istruzione param ( ... )
PowerShell: Parametri
param (
    # parametro obbligatorio
    # si può passare dalla pipeline es.: "324" | .\Template-SgartExample.ps1 -Livello medium 
    [Parameter(Mandatory = $true, ValueFromPipeline = $true)]
    [int]$NecessarioIntero,

    # parametro con valore di default
    [string]$ComputerName = $env:COMPUTERNAME,  

    # parametro che accetta solo valori specifici
    [ValidateSet("Low", "Medium", "High")]
    [Alias("Livello")]
    [string]$ParamLivello,

    # parametro di tipo flag true/false es.: .\Template-SgartExample.ps1 112233 -EnableLog
    [switch]$EnableLog
)
Parameter(Mandatory = $true) definisce il parametro come obbligatorio.

Funzioni per il LOG

Ci sono due comandi principali per il log:
  • Set-ToLog per definire come e dove verranno salvati i file di log
  • Write-ToLog per scrivere nel log e/o su console quando necessario
Per i dettagli vedi Write-ToLog in PowerShell.

Lettura impostazioni da file JSON

L'esempio prevede di leggere dei valori di configurazione, oltre che dai parametri, anche da un file di settings in formato JSON.
JSON: Template-SgartExample.json
{
    "param1": "valore letto dal json"
}
Come convenzione ho imposto che il file JSON deve risiedere nella stessa folder del file PowerShell e con lo stesso nome, ma con estensione .json.
Il file viene letto con:
PowerShell
# ricavo il file settings .json partendo sempre dal percorso del file ps1
$ScriptNameNoExtension = [System.IO.Path]::GetFileNameWithoutExtension($PSCommandPath)
$settingsFileName = "$PSScriptRoot\$scriptNameNoExtension.json"
# leggo il file (Get-Content) e lo converto in un oggetto json (ConvertFrom-Json)
$settings = Get-Content $settingsFileName | ConvertFrom-Json
mentre il singolo valore può essere letto con
PowerShell
$settings.param1

Creazione di funzioni

Le funzioni possono essere create usando la keyword function come mostrato nell'esempio
PowerShell: function
function Get-ServiceRunning {
    param ( ... )
    # leggo i servizi filtrando per quelli in running ("$_" rappresenta il valore corrente)
    # con "|" (pipe) passo i valori di uscita (Get-Service) in ingresso al comando successivo (Where-Object)
    $services = Get-Service | Where-Object { $_.Status -eq "Running" }

    # ritorno il valore
    $services 
}
Attenzione le funzioni DEVONO essere definite prima di essere richiamate.
Se la funzione deve ritronare dei valori è sufficente scrivere la variabile come ultima istruzione (vedi $services).

MAIN PROGRAM

Il corpo principale dell'esempio PowerShell è compreso tra le istruzioni try { ... } catch { ... } finally { ... } dopo il commento # MAIN PROGRAM
PowerShell: Main program
# MAIN PROGRAM

try {
    # esempio di scrittura nel log e su console con Write-ToLog
    Write-ToLog "Start"

    ... inserire qui il codice custom ...

}
catch {
    Write-Error "Error: $($_.ToString())" 
    # in alternativa a $_ usare $Error

    # $errormsg = $_.ToString()
    # $exception = $_.Exception
    # $stacktrace = $_.ScriptStackTrace
    # $failingline = $_.InvocationInfo.Line
    # $positionmsg = $_.InvocationInfo.PositionMessage
    # $pscommandpath = $_.InvocationInfo.PSCommandPath
    # $failinglinenumber = $_.InvocationInfo.ScriptLineNumber
    # $scriptname = $_.InvocationInfo.ScriptName
}
finally {
    # questo lo esegue sempre
    Write-ToLog "End"
}
Nel MAIN ho inserito alcuni esempi di codice PowerShell che ricorrono spesso nelle varie situazioni.

Variabili automatiche

Ci sono varie variabili automatiche che contengno informazioni sull'esecuzione del PowerShell.

Alcune informazioni utili sono queste:
PowerShell
Write-Host "`$PsCmdlet.Host.Version: $($PsCmdlet.Host.Version.ToString())"
Write-Host "`$PsCmdlet.CommandRuntime: $($PsCmdlet.CommandRuntime)"
Write-Host "`$MyInvocation.InvocationName: $($MyInvocation.InvocationName)"
Write-Host "`$MyInvocation.MyCommand: $($MyInvocation.MyCommand)"
Write-Host "`$MyInvocation.MyCommand.Definition: $MyInvocation.MyCommand.Definition (=`$PSCommandPath)"
Write-Host "`$Pwd: $($Pwd)"

Write-Host "`$PSScriptRoot: $PSScriptRoot"
Write-Host "`$PSCommandPath: $PSCommandPath"
Write-Host "`$ScriptNameNoExtension: $ScriptNameNoExtension"

Write-Host "`$PShome: $PShome"

Write-Host "`$PSCulture: $PSCulture"
Se l'esempio viene eseguito con un comando
PowerShell
.\template-base-example\Template-SgartExample.ps1 1
le istruzioni precedenti danno questo risultato
-Runtime parameters--------------------------------------------------
$PsCmdlet.Host.Version: 2021.12.0
$PsCmdlet.CommandRuntime: Template-SgartExample.ps1
$MyInvocation.InvocationName: .\template-base-example\Template-SgartExample.ps1
$MyInvocation.MyCommand: Template-SgartExample.ps1
$MyInvocation.MyCommand.Definition: System.Management.Automation.InvocationInfo.MyCommand.Definition (=$PSCommandPath)
$Pwd: C:\PRJ\PowerShell
$PSScriptRoot: C:\PRJ\PowerShell\template-base-example
$PSCommandPath: C:\PRJ\PowerShell\template-base-example\Template-SgartExample.ps1
$ScriptNameNoExtension: Template-SgartExample
$PShome: C:\Windows\System32\WindowsPowerShell\v1.0
$PSCulture: it-IT
dove di può notare, ad esempio, che la variabile $PWD ritorna il percorso da cui viene lanciato il PowerShell, mentre $PSScriptRoot ritorna sempre il percorso dove risiede lo script .

Per maggiori info sulle variabili automatiche
PowerShell
Help about_Automatic_Variables -Full
Help è un alias del comando Get-Help.

Numeri e date formatati

Quando si ha a che fare con numeri o date sorge anche la necessità di visualizzare gli stessi formattati sendo una specifica culture che non è detto che coincida con quella di sistema.
Tramite il namespace System.Globalization.CultureInfo si può forzare una specifica cultura come ad esempio it-IT
PowerShell
$now = Get-Date
...
# se voglio stampare numeri in un "locale" specifico
Write-Host "`n-Formatting number and date------------------------------------------" -ForegroundColor Yellow

$num = 123456.789
Write-Host "Show a number (no formatting): $num"

$ciIT = new-object System.Globalization.CultureInfo "it-IT"
Write-Host "Show a number (it-IT): $($num.ToString($ciIT))"
Write-Host "Show a date (it-IT): $($now.ToString($ciIT))"

$ciEN = new-object System.Globalization.CultureInfo "en-US"
Write-Host "Show a number (en-US): $($num.ToString($ciEN))"
Write-Host "Show a date (en-US): $($now.ToString($ciEN))"
le istruzioni precedeti danno questo risultato
-Formatting number and date------------------------------------------
Show a number (no formatting): 123456.789
Show a number (it-IT): 123456,789
Show a date (it-IT): 27/12/2021 00:07:02
Show a number (en-US): 123456.789
Show a date (en-US): 12/27/2021 12:07:02 AM

Campi calcolati

A volte può tornare utile creare dei campi calcolati nel comando Select-Object
PowerShell
$services = Get-ServiceRunning | Select-Object -First 10
...
$services | Select-Object Status, @{Name = "Mio campo calcolato"; Expression = { "Nome: $($_.Name) ($($_.DisplayName))" } } | Format-Table
il campo calcolato va definito con l'espressione
PowerShell
@{Name = "..."; Expression = {  ... } }
Potrebbe interessarti anche: