I am using powershell5.1 and I am trying to grab a few tasks running on a remote server and see if any are running. IF there are any running then I will have the script sleep for a minute and then check again until all the tasks I am checking are in Ready status.
What I use to query the remote server:
$servers = "Server1"
""
foreach($server in $servers)
{
Invoke-Command -ComputerName $server -ScriptBlock{
schtasks /query /fo list /tn RandomTaskName
}
}
Which then outputs this result:
HostName: $server
TaskName: \RandomTaskName
Next Run Time: 3/1/2022 11:40:00 AM
Status: Running
Logon Mode: Interactive/Background
So I add a pipe to FINDSTR "Status: Running"
schtasks /query /fo list /tn RandomTaskName | FIDNSTR "Status: Running"
That returns just the string.
Then I try to add the output to a variable and do a foreach loop to see if a string contains "Status: Running"
$servers = "Server1"
foreach($server in $servers)
{
Invoke-Command -ComputerName $provider -ScriptBlock{
schtasks /query /fo list /tn RandomTaskName
}
$task = $_
if ("Status: Running" -ccontains $task)
{
Wrtite-host "Task is still running"
}
else
{
Write-Host "Task is NOT running"
}
}
Which results in nothing. When I comment out the "if" and "else" statements so only "$task=$_" is at the end, then it results in "Status: Running".
Anyone can give any insight as to how to grab the running status for a list of remote servers?
When I saw this question, it really peaked my curiosity. I'm working more with workstations than servers, but everything should be about the same.
This is entirely my approach to solve my own problems, so it may not fit your needs - sorry. But perhaps my code can give you some ideas on how to do what you want.
This code:
Has a function called Invoke-InBackgroundOnMachines that takes an array of machines, a list of arguments, and a scriptblock.
Invoke-InBackgroundOnMachines gets pipeline content from each remote machine that the scriptblock is ran on and returns it on the pipeline.
Invoke-InBackgroundOnMachines checks to see if it can connect to each machine before trying to run the scriptblock on it. Machines that it cannot connect to are returned on the pipeline in a comma delimited text of "Machine", "NoConnection", "NoConnection". Machines that it can connect to are stored in the LiveMachines array, which is later handed to Invoke-Command, along with the scriptblock and argument list.
The one thing I haven't figured out yet is how to best deal with computers that do not have WinRM enabled. Invoke-Command throws errors when it hits a computer with WinRM disabled. This is my first attempt at running remote script blocks, so still have a lot to figure out.
The scriptblock itself wound up being a lot larger than I was expecting. But it does the job of returning the status of tasks in a comma delimited format.
This is not properly tested code. I started with cervan12's code this morning and started writing. So keep that in mind.
# Used Example 8 from following URL
# https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/invoke-command?view=powershell-5.1
$SchTasksScriptBlock = {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string]$TaskNameToFind
)
function CommaDelimited {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Tokens
)
$Return = ''
foreach ($Token in $Tokens) {
$Return += ", `"$Token`""
}
$Return.Substring(2)
}
$STReturn = SCHTASKS /Query /FO LIST /TN $TaskNameToFind 2>$null
if ($LASTEXITCODE -eq 0) {
$HostName = ''
switch -Regex ($STReturn) {
'(?i)^\s*hostname:\s*(?<HostName>.*)\s*$' {
if ($HostName -ne '') { CommaDelimited $HostName, $TaskName, $Status }
$HostName = $Matches.HostName
continue
}
'(?i)^\s*taskname:\s*(?<TaskName>.*)\s*$' {
$TaskName = $Matches.TaskName
continue
}
'(?i)^\s*status:\s*(?<Status>.*)\s*$' {
$Status = $Matches.Status
continue
}
Default {
continue
}
}
if ($HostName -ne '') { CommaDelimited $HostName, $TaskName, $Status }
} else {
$HostName = HostName
CommaDelimited $HostName, $TaskNameToFind, 'Missing'
}
}
function Invoke-InBackgroundOnMachines {
param (
[Parameter(Mandatory = $true, Position = 0)]
[string[]]$Machines,
[Parameter(Mandatory = $true, Position = 1)]
[object[]]$ArgumentList,
[Parameter(Mandatory = $true, Position = 2)]
[scriptblock]$ScriptBlock
)
[string[]]$LiveMachines = #()
foreach ($Machine in $Machines) {
if(Test-Connection -ComputerName $Machine -Quiet) {
$LiveMachines += $Machine
} else {
"`"$Machine`", `"NoConnection`",`"NoConnection`""
}
}
If($LiveMachines.Count -gt 0) {
$Session = New-PSSession -ComputerName $LiveMachines
$null = Invoke-Command -Session $Session -ScriptBlock $ScriptBlock -AsJob -ArgumentList $ArgumentList
Get-Job | Wait-Job | Receive-Job
}
}
Example use:
$List = #( 'Machine1', 'Machine1' )
Invoke-InBackgroundOnMachines $List '\AlertusSecureDesktopLauncher' $SchTasksScriptBlock
Example results returned on the Pipeline:
"Machine1", "\AlertusSecureDesktopLauncher", "Ready"
"Machine2", "\AlertusSecureDesktopLauncher", "Ready"
Related
At [Asynchronous start][1] I had a question about starting a power-shell script asynchronously which creates a form. As answered in that question this can be solved using start-job
eg
Start-Job -ScriptBlock { test NW -NoWarning -Paranoia:2 }
So I have tried to write the Test.PS1 script routine so it re-calls itself with "Start-Job Test -NoSpawn" The switch nospawn then means it runs without a second call. I have tested this with the example code the above line now has to be and it works
Start-Job -ScriptBlock { test NW -NoSpawn -NoWarning -Paranoia:2 }
However I'm struggling to get the parameters from the original command line to passthrough to the job
I have tried creating a string in the correct format , an array , list the arguments manually , I either get repeated arguments being passed or all of the string ending up in the first Parameter $ComputerList -
A summary of the parameters and the attempts are
Param ([string]$ComputerList = 'status\edi.csv',[switch]$NoSpawn,[switch]$NoWarning,[switch]$Debug,[INT]$Paranoia=6)
...... <Snip>
Start-Job -ScriptBlock { test $ComputerList -NoSpawn -NoWarning:$NoWarning -Paranoia:$Paranoia }
Doesn't work due to scope - also switches are wrong way to do this
Start-Job -ScriptBlock { test -NoSpawn $Args } -argumentlist $ComputerList
Insufficent arguments but works - But I think One Argument is possible ?
Start-Job -ScriptBlock { test $Args -NoSpawn } -argumentlist #("-NoWarning:$NoWarning","-ComputerList:$ComputerList","-Paranoia:$Paranoia")
Everything ends up in $ComputerList
Start-Job -ScriptBlock { test $Args -NoSpawn } -argumentlist "-NoWarning:$NoWarning -ComputerList:$ComputerList -Paranoia:$Paranoia"
Everything ends up in $ComputerList
Full code follows
Param ([string]$ComputerList = 'status\edi.csv',[switch]$NoSpawn,[switch]$NoWarning,[switch]$Debug,[INT]$Paranoia=6)
$Log_Paranoia=$Paranoia
If ($Debug) { $debugPreference="Continue"} #enable debug messages if -debug is specified
If ($NoWarning) { $WarningPreference="SilentlyContinue"} #turn off warning messages
function Write-Paranoid($Level, $message) {
$CS=Get-PSCallStack
$Caller = $CS[1]
$Module = "$($Caller.FunctionName)[$($Caller.ScriptLineNumber)]"
$Diff=$level - $Log_Paranoia
$MSG= "$Module($($Level),$($Log_Paranoia)):$message"
if ($level - $Log_Paranoia -le 0 ) {
Write-host $MSG
}
if($Error.Count -gt 0 ) {
$MSG= "$Module($Level)ERROR:$Error[0]"
Write-Error $MSG
}
$error.clear()
}
Function AddStatusBar($form , $Txt) {
Write-Paranoid 10 "Enter"
$statusBar = New-Object System.Windows.Forms.StatusBar
$statusBar.DataBindings.DefaultDataSourceUpdateMode = 0
$statusBar.TabIndex = 4
$statusBar.Size = SDS 428 22
$statusBar.Location = SDP 0 337
$statusBar.Text = $Txt
$form.Controls.Add($statusBar)
$statusBar
Write-Paranoid 10 "Exit"
}
Function Create-Form ($Title)
{
Write-Paranoid 10 "Enter"
$form1 = New-Object System.Windows.Forms.Form
$form1.Text = $Title
$form1.DataBindings.DefaultDataSourceUpdateMode = 0
$form1.ClientSize = SDS 890 359
$form1.StartPosition = 0
$form1.BackColor = [System.Drawing.Color]::FromArgb(255,185,209,234)
$form1
Write-Paranoid 10 "Exit"
}
Function GenerateTestForm
{
Write-Paranoid 10 "Enter"
[reflection.assembly]::loadwithpartialname("System.Drawing") | Out-Null
[reflection.assembly]::loadwithpartialname("System.Windows.Forms") | Out-Null
$Form1 = Create-Form "Test Form"
$Alist = Get-CommandLine
$StatusBar = AddStatusBar $form1 $AList
$form1.ShowDialog() | Out-Null # Suspends calller
Write-Paranoid 10 "Exit"
}
if ($NoSpawn )
{
Write-Paranoid 3 " NoSpawn "
Write-Paranoid 5 "Call GenerateForm"
if ($Test) {
GenerateTestForm
} else {
GenerateTestForm
}
} else {
Write-Paranoid 3 "NOT NoSpawn restarting as job"
# note that test.ps1 is in the path so it will restart this script
# Start-Job -ScriptBlock { test $ComputerList -NoSpawn -NoWarning:$NoWarning -Paranoia:$Paranoia } #Wrong scope
# Start-Job -ScriptBlock { test -NoSpawn $Args } -argumentlist $ComputerList # Insufficent aruments but works - ONLY One Argument possible -
# Start-Job -ScriptBlock { test $Args -NoSpawn } -argumentlist #("-NoWarning:$NoWarning","-ComputerList:$ComputerList","-Paranoia:$Paranoia") # Everything ends up in $ComputerList
# Start-Job -ScriptBlock { test $Args -NoSpawn } -argumentlist "-NoWarning:$NoWarning -ComputerList:$ComputerList -Paranoia:$Paranoia" # Everything ends up in $ComputerList
}
Your problem can be reduced to this:
How can I re-invoke the script at hand as a background job, passing all original arguments (parameter values, including default parameter values) through?
A simplified example:
param (
[string] $ComputerList = 'status\edi.csv',
[switch] $NoSpawn,
[switch] $NoWarning,
[switch] $Debug,
[int] $Paranoia=6
)
if ($NoSpawn) { # already running as a background job.
"I'm now running in the background with the following arguments:"
$PSBoundParameters
} else { # must re-invoke via a background job
# Add *default* parameter values, if necessary, given that
# they're *not* reflected in $PSBoundParameters.
foreach ($paramName in $MyInvocation.MyCommand.Parameters.Keys) {
if (-not $PSBoundParameters.ContainsKey($paramName)) {
$defaultValue = Get-Variable -Scope Local -ValueOnly $paramName
if (-not ($null -eq $defaultValue -or ($defaultValue -is [switch] -and -not $defaultValue))) {
$PSBoundParameters[$paramName] = $defaultValue
}
}
}
# Start a background job that reinvokes this script with the original
# arguments / default values.
Start-Job {
$argsHash = $using:PSBoundParameters
& $using:PSCommandPath -NoSpawn #argsHash
} |
Receive-Job -Wait -AutoRemoveJob
}
Note:
For demonstration purposes, the initial call waits for the re-invocation via a background job to finish, using Receive-Job -Wait -AutoRemoveJob
In your real code, you can simply discard Start-Job's output (a job-information object) with $null = Start-Job { ... }, and then rely on the job getting cleaned up when the caller's session as a whole exits.
The extra code needed to propagate parameter default values is somewhat cumbersome, but necessary, given that the automatic $PSBoundParameters variable does not reflect default values.
GitHub issue #3285 discusses this limitation, and suggests a potential future solution.
I've written the following function in test.ps1 and I would like to make a choise when running thsi script to start/stop/.. :
function getState($SeviceName) {
$server = #('host_1', 'host_2')
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
I would like to provide $ServiceName as argument (with stdin) how can I do it? => somthing like choose 1 to start 2 to stop ...
To use switch/case in Powershell
$doAction = {"Stop-Service", "Start-service"}
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName | $doAction}
How do I use the switch to select start or stop?
Here's a function that will do what you're asking for:
function Get-State {
[CmdletBinding()]
[OutputType('System.ServiceProcess.ServiceController')]
param(
[Parameter(Position = 0, Mandatory)]
[ValidateSet('Start', 'Stop', 'Get')]
[string] $Action,
[Parameter(Position = 1, ValueFromPipeline, Mandatory)]
[ValidateNotNullOrEmpty()]
[string] $ServiceName
)
begin {
$serverList = #('host_1', 'host_2')
}
process {
foreach ($server in $serverList) {
try {
$svc = Get-Service -ComputerName $server -Name $ServiceName -ErrorAction Stop
} catch {
throw "Failed to find service $ServiceName on $server! $PSItem"
}
switch ($Action) {
'Start' { $svc | Start-Service -PassThru }
'Stop' { $svc | Stop-Service -Force -PassThru }
default { $svc }
}
}
}
}
It utilizes advanced function features and attributes to take pipeline input (stdin in your words). I'd suggest reading this documentation.
You can add argument to a script by adding parameters to it.
On the top of your script file put:
Param
(
[parameter()]
[String[]]$YourArgumentVariable
[parameter()]
[switch] $MySwitch
)
With a function it goes right after the function definition. So in your case:
function getState($SeviceName) {
Param
(
[parameter()]
[String[]]$server
[parameter()]
[switch] $MySwitch
)
# get status
$server | % {Write-Host "verify: $_"; Get-Service -ComputerName $_ -Name SeviceName
}
A switch basically sets a boolean to true or false.
So in this if you call the script with -MySwitch it will set the variable $MySwitch to true. Else it will remain false.
Don Jones has written a good getting started article on paramters that I would recommend you checking out.
Do note that there are loads of things you can define in the paramter. Like if you want to make sure it is always filled you can set
[parameter(Mandatory=$true)]
This is just one of many examples of what you can do with paramters.
This lambda function executes as expected:
$WriteServerName = {
param($server)
Write-Host $server
}
$server = "servername"
$WriteServerName.invoke($server)
servername
However, using the same syntax, the following script prompts for credentials and then exits to the command line (running like this: .\ScriptName.ps1 -ConfigFile Chef.config), implying that the lambda functions aren't executing properly (for testing, each should just output the server name).
Why does the former lambda function return the server name, but the ones in the script don't?
Param(
$ConfigFile
)
Function Main {
#Pre-reqs: get credential, load config from file, and define lambda functions.
$jobs = #()
$Credential = Get-Credential
$Username = $Credential.username
$ConvertedPassword = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($Credential.password)
$Password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($ConvertedPassword)
$Config = Get-Content $ConfigFile -Raw | Out-String | Invoke-Expression
#Define lambda functions
$BootStrap = {
param($Server)
write-host $server
}
$RunChefClient = {
param($Server)
write-host $server
}
$SetEnvironment = {
param($Server)
write-host $server
}
#Create bootstrap job for each server and pass lambda functions to Scriptblock for execution.
if(($Username -ne $null) -and ($Password -ne $null))
{
ForEach($HashTable in $Config)
{
$Server = $HashTable.Server
$Roles = $HashTable.Roles
$Tags = $HashTable.Tags
$Environment = $HashTable.Environment
$ScriptBlock = {
param ($Server,$BootStrap,$RunChefClient,$SetEnvironment)
$BootStrap.invoke($Server)
$RunChefClient.invoke($Server)
$SetEnvironment.invoke($Server)
}
$Jobs += Start-Job -ScriptBlock $ScriptBlock -ArgumentList #($Server,$BootStrap,$RunChefClient,$SetEnvironment)
}
}
else {Write-Host "Username or password is missing, exiting..." -ForegroundColor Red; exit}
}
Main
Without testing, I am going to go ahead and say it's because you are putting your scriptblock executions in PowerShell Jobs and then not doing anything with them. When you start a job, it starts a new PowerShell instance and executes the code you give it along with the parameters you give it. Once it completes, the completed PSRemotingJob object sits there and does nothing until you actually do something with it.
In your code, all the jobs you start are assigned to the $Jobs variable. You can also get all your running jobs with Get-Job:
Get-Job -State Running
If you want to get any of the data returned from your jobs, you'll have to use Receive-Job
# Either
$Jobs | Receive-Job
# Or
Get-Job -State Running | Receive-Job
I know that you can't use Read-Host within a Workflow. But I need to prompt the user via Read-Host and then use that input data within a Workflow. We need to execute MSG.EXE on remote machines (since NetSend went bye bye)
I have this code. It runs without error but it does Not send any message. how do I pass my argument into the Workflow so it works?
$Computers = #("mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257")
workflow Test-WFConnection {
param(
[Parameter (Mandatory = $true)]
[object[]]$message
)
foreach -parallel ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {msg /SERVER:$computer * $msg}
}
}
$msg = Read-host "Enter your message here"
Test-WFConnection -message $msg
The issue is the $msg never gets sent to any machine.
To deal with workflows you have to understand the scopes in workflow. Here, the value of $msg is not with the scope thats why the value is not at all coming inside the block. So you have to use $Using:msg , then it will be available.
I will give a small example below to clarify your doubt:
workflow sample_test
{
$variable1 = 5
# Changes to variable value in an InlineScript do not affect
# the value of the workflow variable.
InlineScript {$variable1 = $Using:variable1 + 1; "Inline Variable1 = $variable1"}
"Workflow variable1 = $variable1"
# To change the value in workflow scope, return the new value.
$a = InlineScript {$variable1 = $Using:variable+1; $variable1}
"Workflow New variable1 = $variable1"
}
Below is the screenshot for your reference:
Hope it helps...!!!
How about you declare the computers in the workflow?
workflow send-message {
param(
[Parameter (Mandatory = $true)]
[string]$message
)
$computers = "mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257"
foreach -parallel ($computer in $computers){
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue){
msg /SERVER:$computer * $message
}}}
send-message -message (read-host 'Enter message')
This worked ok when I tested it.
As posted here: https://community.spiceworks.com/topic/1951356-pass-argument-to-workflow
you can pass the computers as parameter like this or is there any limitation that stops you from doing that:
$Computers = #("mg2014","cclab2","MG11751","mg10462","mg11768","mg11786","mg11741","mg13244","mg13434","mg14464", "mg10257")
workflow Test-WFConnection {
param(
[Parameter (Mandatory = $true)]
[object[]]$message,[object[]]$computers
)
foreach -parallel ($computer in $computers) {
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {msg /SERVER:$computer * $msg}
}
}
$msg = Read-host "Enter your message here"
Test-WFConnection -message $msg -computers $Computers
I'm not certain what is happening with these answers - they're a bit obscure.
Neal5's answer was the correct answer, but without actually explaining why (although I would also suggest adding the list of computers as a parameter as in Abhijith pk's answer).
The issue is the statement that is run if the condition is met:
if (Test-Connection -ComputerName $computer -Count 1 -ErrorAction SilentlyContinue) {
msg /SERVER:$computer * $msg
}
You're using $msg here, however you have named the parameter $message:
param(
[Parameter (Mandatory = $true)]
[object[]]$message
)
So you're statement should read:
msg /SERVER:$computer * $message
I have a system in which I'm remoting into a single machine at a time and running commands, scripts, etc. It would be useful to be able to effectively return log messages from the remote script in "realtime". Some code to get an idea of what I'm trying to do.
Note that both the local Log-*Msg functions log to a database (and tee to standard out/err as appropriate). Also note that we have analogous Log-*Msg methods on the remote side (loaded from a module) that are meant to pitched back across the wire and recorded in the DB as if the local Log-*Msg function was called.
Local Methods
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
if($_ -isnot [string]) { return $_ }
if($_.StartsWith('Log-VerboseMsg:')) {
Log-VerboseMsg $_.Replace("Log-VerboseMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-WarningMsg:')) {
Log-WarningMsg $_.Replace("Log-WarningMsg:", "") | Out-Null
return
}
if($_.StartsWith('Log-UserMsg:')) {
Log-UserMsg $_.Replace("Log-UserMsg:", "") | Out-Null
return
}
else { return $_ }
}
Example Remote Method
On the remote side I have a module that gets loaded with a few logging functions, here's one such function:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
"Log-VerboseMsg:$msg"
}
For the most part it works, I can do the following
$val = Exec-Remote -Session $PSSession {
Log-VerboseMsg 'A test log message!'
return $true
}
And have it do the right thing transparently.
However, it fails in the following scenario.
$val = Exec-Remote -Session $PSSession {
function Test-Logging {
Log-VerboseMsg 'A test log message!'
return $true
}
$aVariable = Test-Logging
Do-ALongRunningOperation
return $aVariable
}
The above will not return anything until the 'long running operation' completes.
My question to you is the following.
Is there a way for me to reliably do this in Powershell? In some form, if the approach I'm using is really that terrible, feel free to lambast me and explain why.
NOTE: connecting to the DB from the remote environment and recording the log messages will not always be possible, so while that approach could work, for my specific needs it isn't sufficient.
In PowerShell v5 you can use new information stream for this. You should modify local functions as following:
function Exec-Remote {
param(
[ValidateNotNull()]
[System.Management.Automation.Runspaces.PSSession]
$Session=$(throw "Session is mandatory ($($MyInvocation.MyCommand))"),
$argumentList,
$scriptBlock
)
if($argumentList -is [scriptblock]) {$scriptBlock = $argumentList}
if($scriptBlock -eq $null) { throw 'Scriptblock is required'}
# 6>&1 will redirect information stream to output, so Filter-RemoteLogs can process it.
Invoke-Command -Session $Session -ArgumentList $argumentList -scriptBlock $scriptBlock 6>&1 | Filter-RemoteLogs
}
Filter Filter-RemoteLogs {
# Function should be advanced, so we can call $PSCmdlet.WriteInformation.
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline)]
[PSObject]$InputObject
)
if(
# If it is InformationRecord.
($InputObject -is [Management.Automation.InformationRecord]) -and
# And if it come from informational steam.
($WriteInformationStream=$InputObject.PSObject.Properties['WriteInformationStream']) -and
($WriteInformationStream.Value)
) {
# If it is our InformationRecord.
if($InputObject.Tags-contains'MyLoggingInfomation') {
# Write it to log.
&"Log-$($InputObject.MessageData.LogType)Msg" $InputObject.MessageData.Message | Out-Null
} else {
# Return not our InformationRecord to informational stream.
$PSCmdlet.WriteInformation($InputObject)
}
} else {
# Return other objects to output stream.
$PSCmdlet.WriteObject($InputObject)
}
}
And remote logging functions should write to information stream:
function Log-VerboseMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]#{Message=$msg;LogType='Verbose'}) MyLoggingInfomation
}
function Log-WarningMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]#{Message=$msg;LogType='Warning'}) MyLoggingInfomation
}
function Log-UserMsg {
param([ValidateNotNullOrEmpty()] $msg)
Write-Information ([PSCustomObject]#{Message=$msg;LogType='User'}) MyLoggingInfomation
}