Template script PowerShell
Questo è un template base da cui partire per creare script PowerShell con il supporto per il log su file.
Nello script ci sono vari esempi tra cui:
e può essere visualizzato tramite il comando
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:
mentre il singolo valore può essere letto con
Nel MAIN ho inserito alcuni esempi di codice PowerShell che ricorrono spesso nelle varie situazioni.
Alcune informazioni utili sono queste:
Se l'esempio viene eseguito con un comando
le istruzioni precedenti danno questo risultato
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
Tramite il namespace System.Globalization.CultureInfo si può forzare una specifica cultura come ad esempio it-IT
le istruzioni precedeti danno questo risultato
il campo calcolato va definito con l'espressione
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"
}
- 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
#>
...
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
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"
}
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
PowerShell
$settings.param1
Creazione di funzioni
Le funzioni possono essere create usando la keyword function come mostrato nell'esempioPowerShell: 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 PROGRAMPowerShell: 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"
}
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"
PowerShell
.\template-base-example\Template-SgartExample.ps1 1
Text
-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
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))"
Text
-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-ObjectPowerShell
$services = Get-ServiceRunning | Select-Object -First 10
...
$services | Select-Object Status, @{Name = "Mio campo calcolato"; Expression = { "Nome: $($_.Name) ($($_.DisplayName))" } } | Format-Table
PowerShell
@{Name = "..."; Expression = { ... } }