I tried it, and yes it's possible. Well, you would still need to add 2-lines of code, and this doesn't necessarily mean that you can just copy paste any piece of code in there (for example the parameters of your script must be strong-typed and must match the support parameters, there are other native wfa cmdlets like "connect-wfacluster", ...)
But, to come back to the original question, yes it's possible to override Write-Host.
Custom Module
First of all this requires a custom piece of code that is available from within WFA. I would recommend to create you own "WFAWrapper"-script. I called mine "CustomWfaWrapper.psm1". That's correct, I made it a module. This way I can make many other re-usable functions and by bundling them into a single module, my custom code becomes available by simple doing a "Import-Module CustomWfaWrapper".To create a custom module, you need to create a .psm1 file and add your functions in there (just plain text, with any editor you please). Then you need put that file in a folder with the exact same name (drop the extension), in our case "CustomWfaWrapper\CustomWfaWrapper.psm1"
Finally you need to copy that folder somewhere accessible. Either you can put it in the overall Microsoft modules folder (C:\Windows\System32\WindowsPowerShell\v1.0\Modules), then you can just call it like "Import-Module CustomWfaWrapper". Or you can copy your folder to the WFA modules folder (C:\Program Files\NetApp\WFA\PoSH\Modules), which might be a bit more organized, and then you'll need to call it from this path "Import-Module "..\..\..\..\PoSH\Modules\CustomWfaWrapper"
Override Write-Host
Next, we need to override the Write-Host cmdlet.
The order of processing code in PowerShell is :
Explanation :
So I add my own custom write-host (write-customhost, choose whatever fits for you and make it unique, as it's global)
Next I add 2 functions, one that creates an alias "Write-Host" and the other that removes that alias.
As the alias is also global and is at the top of the executionlist, you have now overridden Write-Host.
The order of processing code in PowerShell is :
- Alias
- Function
- Cmdlet
- Native Windows Command
So a proper way of doing this is creating a custom function/cmdlet and then give it the alias "Write-Host".
Now, I don't like the fact that the override is just always there, as soon as we import our module, I rather like something like "Remove-WriteHost" and "Restore-WriteHost". So here it is, just paste these in you custom wfa wrapper module, along with all your other fancy custom functions.
function global:Write-CustomHost{ [CmdletBinding(PositionalBinding=$true)] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Object]$Object, [ConsoleColor]$BackgroundColor, [ConsoleColor]$ForegroundColor, [switch]$NoNewline, [object]$Separator ) Begin{} Process { Get-WfaLogger -info -message ($Object.ToString()) } End{} } function Remove-WriteHost { New-Alias -Name "Write-Host" -Value "Write-CustomHost" -Scope global } function Restore-WriteHost { Del Alias:\Write-Host }
Explanation :
So I add my own custom write-host (write-customhost, choose whatever fits for you and make it unique, as it's global)
Next I add 2 functions, one that creates an alias "Write-Host" and the other that removes that alias.
As the alias is also global and is at the top of the executionlist, you have now overridden Write-Host.
Usage Example
So if you have an existing piece of powershell, where you use "write-host" a lot, you can now simple add the lines :
- import-module CustomWfaWrapper
- remove-writehost
If you would, for some reason, want to undo this, you simple invoke "restore-writehost".
Extra
My override function is invoking the get-wfalogger, but you could extend this with writing to logfiles, the registry, etc... Infact, in many of my code, I used log4net, which allows advanced logging.
We are only overriding write-host, but you can also override write-warning & write-error if you wanted to.
Below is a sample, where I override "start-transcript and stop-transcript" which will start logging write-host to a text-file. I'm using log4net, so you can start setting thresholds, patterns, rolling logs, etc... I'm even abusing the write-host color code to determine if it's info, warning, error.
By using this, you can now do something like :
Below is a sample, where I override "start-transcript and stop-transcript" which will start logging write-host to a text-file. I'm using log4net, so you can start setting thresholds, patterns, rolling logs, etc... I'm even abusing the write-host color code to determine if it's info, warning, error.
By using this, you can now do something like :
New-Variable -Name logg -Scope global Import-Module CustomWfaWrapper Remove-WriteHost Remove-StartStopTranscript Start-Transcript -Path "c:\temp\test.txt" write-host "wfaguy is awesome" Stop-Transcript Restore-WriteHost Restore-StartStopTranscript
The script above will now override write-host for WFA and also write it to a rolling logfile. And since start-transcript is a native powershell cmdlet, it will still work outside WFA.
Below is the new module wrapper
New-Variable -Name logg -Scope global New-Variable -Name transcriptStarted -Scope global function global:Write-CustomHost{ [CmdletBinding(PositionalBinding=$true)] param( [Parameter(Mandatory=$true, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true, ValueFromRemainingArguments=$false, Position=0)] [ValidateNotNull()] [ValidateNotNullOrEmpty()] [Object]$Object, [ConsoleColor]$BackgroundColor, [ConsoleColor]$ForegroundColor, [switch]$NoNewline, [object]$Separator ) Begin{} Process { Get-WfaLogger -info -message ($Object.ToString()) if($global:transcriptStarted){ if($ForegroundColor -eq "yellow"){ $global:logg.Warn($Object.ToString()) }elseif($ForegroundColor -eq "red"){ $global:logg.Error($Object.ToString()) }else{ $global:logg.Info($Object.ToString()) } } } End{} } function Remove-WriteHost { New-Alias -Name "Write-Host" -Value "Write-CustomHost" -Scope global } function Restore-WriteHost { Del Alias:\Write-Host } function SetLog4NetEventLogEventId{ Param ( [Parameter(Mandatory=$true)] [int] $EventId, [Parameter(Mandatory=$true)] [int] $Category ) [log4net.ThreadContext]::Properties.Item("EventID") = $EventId [log4net.ThreadContext]::Properties.Item("Category") = $Category } function global:Start-CustomTranscript{ [CmdletBinding()] param( [string]$Path, [switch]$Append, [switch]$Force, [switch]$NoClobber, [switch]$IncludeInvocationHeader, [switch]$LogToEventViewer ) $LoggerName = "transcript" $dllLocation = "C:\Program Files\NetApp\WFA\PoSH\Modules\DataONTAP\log4net.dll" $logfile = $Path # Initialize log4net [Reflection.Assembly]::LoadFrom($dllLocation) | Out-Null #Reset the log4net configuration [log4net.LogManager]::ResetConfiguration() #Define new logger for this module only try{ [log4net.LogManager]::CreateRepository("$($LoggerName)Logger") }catch{ Get-WfaLogger -warn -message "[LOG] Repository already created" } [log4net.Repository.Hierarchy.Hierarchy]$repository = [log4net.LogManager]::GetRepository("$($LoggerName)Logger") # create new appenders $repository.Root.RemoveAllAppenders(); $global:transcriptStarted = $true # create rolling file logging Get-WfaLogger -info -message "[LOG] LogFile path is : '$logFile'" New-Item -Path $logFile -type file -ErrorAction SilentlyContinue $logPattern="%d %w %-5p %c : %m%n" $rollingLogAppender = new-object log4net.Appender.RollingFileAppender $rollingLogAppender.MaximumFileSize = "50MB" $rollingLogAppender.Name = "file" $rollingLogAppender.File = $logFile $rollingLogAppender.RollingStyle = "Size" $rollingLogAppender.StaticLogFileName = $true $rollingLogAppender.MaxSizeRollBackups = "20" $rollingLogAppender.Layout = new-object log4net.Layout.PatternLayout($logPattern) $rollingLogAppender.Threshold = [log4net.Core.Level]::Debug $rollingLogAppender.ActivateOptions() $repository.Root.AddAppender($rollingLogAppender) Get-WfaLogger -info -message "[LOG] File Logger initialized" if($LogToEventViewer){ # create eventlog logging $eventAppender = new-object log4net.Appender.EventLogAppender $eventAppender.Name = "event" $eventAppender.ApplicationName = "$($LoggerName)" $eventAppender.EventId = 1 $eventAppender.Layout = new-object log4net.Layout.PatternLayout($eventPattern) $eventAppender.Threshold = [log4net.Core.Level]::Debug $eventAppender.ActivateOptions() $repository.Root.AddAppender($eventAppender) Get-WfaLogger -info -message "[LOG] Event viewer Logger initialized" } # mark as configured $repository.Configured = $true $global:logg=[log4net.LogManager]::GetLogger("$($LoggerName)Logger","$($LoggerName)") Get-WfaLogger -info -message "[LOG] Logger initialized" if($LogToEventViewer){ SetLog4NetEventLogEventId -EventId $EventId -Category $Category } } function global:Stop-CustomTranscript{ $Global:logg = $null $global:transcriptStarted = $false } function Remove-StartStopTranscript { New-Alias -Name "Start-Transcript" -Value "Start-CustomTranscript" -Scope global New-Alias -Name "Stop-Transcript" -Value "Stop-CustomTranscript" -Scope global } function Restore-StartStopTranscript { Del Alias:\Start-Transcript Del Alias:\Stop-Transcript }
No comments :
Post a Comment