Issue with output of Powershell function - function

I'm writing a function and observing some unusual behavior with the output.
This is the code:
Function Get-CompInfo
{
[Cmdletbinding()]
PARAM
(
[Parameter(ValueFromPipeline=$true)]
[Alias('Comp', 'Host')]
[string[]]$computername
)
Begin
{
}
Process
{
if ($computername -eq $Null) {
$computername=$env:computername
$VerboseOut="No computer specified. Running against local computer $computername :"
}
Else {
$VerboseOut="Getting information for computer $computername :"
}
Write-Verbose $VerboseOut
$CompInfo=Get-WmiObject Win32_Computersystem -computername $computername
$OSInfo=Get-WmiObject Win32_OperatingSystem -computername $computername
$Properties = [ordered]#{
'Input'=$computername
'SystemName'=$CompInfo.Name
'Manufacturer'=$CompInfo.Manufacturer
'Model'=$CompInfo.Model
'PhysicalMemory'=$CompInfo.TotalPhysicalMemory
'LogicalProcessors'=$CompInfo.NumberOfLogicalProcessors
'OSCaption'=$OSInfo.Caption
'OSArchitecture'=$OSInfo.OSArchitecture
'ServicePackMajorVersion'=$OSInfo.ServicePackMajorVersion}
# Output Information
$obj=New-Object -TypeName PSobject -Property $Properties
write-output $obj
}
End
{
}
}
When passing parameters from the pipline:
"karuma", "localhost" | Get-CompInfo
Input : {karuma}
SystemName : KARUMA
Manufacturer : Hewlett-Packard
Model : h9-1400a
PhysicalMemory : 17115000832
LogicalProcessors : 8
OSCaption : Microsoft Windows 10 Pro
OSArchitecture : 64-bit
ServicePackMajorVersion : 0
Input : {localhost}
SystemName : KARUMA
Manufacturer : Hewlett-Packard
Model : h9-1400a
PhysicalMemory : 17115000832
LogicalProcessors : 8
OSCaption : Microsoft Windows 10 Pro
OSArchitecture : 64-bit
ServicePackMajorVersion : 0
I get the same kind of output when I pass a text file with a list of computer names.
When specifying multiple host names something different:
Get-CompInfo -computername localhost, karuma
Input : {localhost, karuma}
SystemName : {KARUMA, KARUMA}
Manufacturer : {Hewlett-Packard, Hewlett-Packard}
Model : {h9-1400a, h9-1400a}
PhysicalMemory : {17115000832, 17115000832}
LogicalProcessors : {8, 8}
OSCaption : {Microsoft Windows 10 Pro, Microsoft Windows 10 Pro}
OSArchitecture : {64-bit, 64-bit}
ServicePackMajorVersion : {0, 0}
I'm expecting to see table output when passing multiple values as would by seen by piping to format-table.
Any help on what I need to change to get the output as desired would be appreciated.

It's the way you have your function setup... when using the pipeline the PROCESS block will be run once for each object in the pipeline - the $computername param will only contain a single object at a time in this situation.
When you specify two computers in $computername param, you are changing the way the entire function is running as it contains two objects.
It's easy to fix by wrapping your function in a Foreach like so:
Function Get-CompInfo
{
[Cmdletbinding()]
PARAM
(
[Parameter(ValueFromPipeline=$true)]
[Alias('Comp', 'Host')]
[string[]]$computername
)
Begin {}
Process
{
Foreach ($computer in $computername) {
if ($computer -eq $Null) {
$computer=$env:computername
$VerboseOut="No computer specified. Running against local computer $computer :"
}
Else {
$VerboseOut="Getting information for computer $computer :"
}
Write-Verbose $VerboseOut
$CompInfo=Get-WmiObject Win32_Computersystem -computername $computer
$OSInfo=Get-WmiObject Win32_OperatingSystem -computername $computer
$Properties = [ordered]#{
'Input'=$computer
'SystemName'=$CompInfo.Name
'Manufacturer'=$CompInfo.Manufacturer
'Model'=$CompInfo.Model
'PhysicalMemory'=$CompInfo.TotalPhysicalMemory
'LogicalProcessors'=$CompInfo.NumberOfLogicalProcessors
'OSCaption'=$OSInfo.Caption
'OSArchitecture'=$OSInfo.OSArchitecture
'ServicePackMajorVersion'=$OSInfo.ServicePackMajorVersion}
# Output Information
$obj=New-Object -TypeName PSobject -Property $Properties
write-output $obj
}
}
End {}
}

Related

Use a parameter switch to change how a function behaves

My main PowerShell code runs a function that logs to the Windows eventlog. If the level is error it uses a separate event ID which then our monitoring will pick up that exact ID and run an action. However, if I want to specify in the parameter of the main script (not the function) that this time running it use a different Event ID so it will NOT action monitoring, I don't know where to even start on that.
Is there a way to provide a switch parameter in the main script like $NoAlert which then changes the Event ID in the function?
The function of logging lives in a PowerShell module I created. I am importing the module at the beginning of the script and then calling the function during the main script body.
Here is the function:
function WriteLog-SRTProd {
Param(
[string]$logT,
[Parameter(Mandatory=$true)][string]$level,
[String]$LogFileDirT = "\\ServerA\Logs"
)
$RSLogfileT = (Get-ChildItem -Path $LogFileDirT |
sort LastWriteTime |
select -Last 1).Name
## make sure a level is correctly selected (mandatory)
if ("Error","Info","Warn" -NotContains $Level) {
throw "$($Environment) is not a valid name! Please use 'Error', 'Warn', or 'Info'"
}
if ($Level -eq "Info") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) INFO $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Information -EventId 100 -Message $logT -Category 0
}
if ($Level -eq "Warn") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) WARN $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Warning -EventId 200 -Message $logT -Category 0
}
if ($Level -eq "Error") {
Add-Content -Path "$LogFileDirT\$RSLogFileT" -Value "$(Get-Date -format MM-dd-yyyy::HH:mm:ss) ERROR $logT"
Write-EventLog -LogName Application -Source TEST_MAINT -EntryType Error -EventId 300 -Message $logT -Category 0
}
}
I'd like to run my script like this. When the $NoAlert is passed, it will send that switch to the function. Is this possible? Can I just add the switch in both places and use an if statement in the function for when the NoAlert switch is used?
PS C:\> .\Maintenance.ps1 -NoAlert
Param(
[switch]$NoAlert
)
WriteLog-SRTProd -level Error -logT "Custom Error Message"
I have created own function for logging and stored/installed as module, below is the part of my log module :
you can customize the write statements and add your code for event log. I have added 'NoAction' enum member as per your requirements.
I have used one Enum to separate the log levels
Enum Severity
{
Error = 3
Warning = 4
Informational = 6
Debug = 7
Verbose = 8
NoAction = 0 # AS PER YOUR REQUIREMENTS
}
function Write-Log()
{
[cmdletbinding()]
param
(
[Parameter(Position=0,mandatory=$true)]
[Severity] $LogLevel,
[Parameter(Position=1,mandatory=$true)]
[String] $Message
)
$TimeStamp = "$(Get-Date -format HH:mm:ss)" ;
Switch($LogLevel)
{
([Severity]::Error.ToString())
{
Write-Error "`t$TimeStamp : $Message`n" -ErrorAction Stop
break;
}
([Severity]::Warning.ToString())
{
Write-Warning "`t$TimeStamp : $Message`n" -WarningAction Continue
break;
}
([Severity]::Informational.ToString())
{
Write-Information "INROMATION:`t$TimeStamp : $Message`n" -InformationAction Continue
break;
}
([Severity]::Verbose.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
([Severity]::NoAction.ToString())
{
Write-Verbose "`t$TimeStamp : $Message`n"
break;
}
} # END OF SWITCH
} # END OF FUNCTION
Sample Call :
Write-Log -LogLevel ([Severity]::Informational) -Message "test log message using info level"
Output :
INROMATION: 09:40:15 : test log message using info level
I have decided to just add a new parameter to both function and main script named $NoAlert. I have added an If($NoAlert){WriteLog-SRPProd -NoAlert} to the main script (messy, but its what I needed done). then in the Function, If($NoAlert){EventID 111}. so basically I am using the switch in the main script that then calls the NoAlert switch in the function. This is all done with a few added If/Else statements.
Hopefully that makes sense. Like I said its not the best answer, but I wanted to get it done and still provide an answer here in this post.

Add firewall rule to Azure Database for MySQL server from powershell

I have deployed Azure Database for MySQL server and I want to add a firewall rule to it. I need to do it from PowerShell because this step is part of the greater solution. I tried the following code:
$resource = Get-AzureRmResource -ResourceGroupName $ResourceGroup.Variables.ResourceGroup `
-ResourceType "Microsoft.DBforMySQL/servers" -ResourceName $MySQLServer.ResourceName
$props = $resource.Properties
$props | Add-Member #{ipV4FirewallSettings = [ordered] #{ "firewallRules" = #() } }
$props.ipV4FirewallSettings.firewallRules = $MySQLServer.FirewallRules
$props | Add-Member #{administratorLoginPassword = "Qwerty123!" }
Set-AzureRmResource -PropertyObject $props -ResourceGroupName $ResourceGroup.Variables.ResourceGroup `
-ResourceType "Microsoft.DBforMySQL/servers" -ResourceName $MySQLServer.ResourceName -Force
Where $MySQLServer.FirewallRules are from json file in the following format:
"FirewallRules" : [
{ "firewallRuleName" : "test", "rangeStart": "0.0.0.0", "rangeEnd": "0.0.0.0" },
{ "firewallRuleName" : "test2", "rangeStart": "0.0.0.1", "rangeEnd": "255.255.255.255" }
],
This code does not throw any error, but it's not adding rules to the resource.
I need a pointer to where I made a mistake or some documentation, how to handle such task properly.
You can use New-AzureRmResource command to add Mysql firewall rules:
PS C:\Users\jason> $b = New-Object Psobject -Property #{startIpAddress="172.0.0.1" ; endIpAddress="172.0.0.8"}
PS C:\Users\jason> $b
startIpAddress endIpAddress
-------------- ------------
172.0.0.1 172.0.0.8
PS C:\Users\jason> New-AzureRmResource -ResourceId "/subscriptions/b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f/resourceGroups/jasonmysql/providers/Microsoft.DBforMySQL/servers/jasonmysql/firewallRules/rule2" -Properties $b -ApiVer
sion 2017-04-30-preview -Force
Name : rule2
ResourceId : /subscriptions/b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f/resourceGroups/jasonmysql/providers/Microsoft.DBforMySQL/servers/jasonmysql/firewallRules/rule2
ResourceName : jasonmysql/rule2
ResourceType : Microsoft.DBforMySQL/servers/firewallRules
ResourceGroupName : jasonmysql
SubscriptionId : b83c1ed3-xxxx-xxxx-xxxx-2b83a074c23f
Properties : #{startIpAddress=172.0.0.1; endIpAddress=172.0.0.8}

Make function for workflow - Powershell

I'm trying to speed up script for checking disk SMART prefail status, because I need to check two thousand computers.
However the script is still writing out status for the same disk - computers "hostname1" and "hostname2" has different disks.
function disk-status (){
param(
[Parameter(Mandatory=$true)][string]$computername
)
$WMI = Get-WMIObject -Class Win32_DiskDrive
ForEach ($Drive in $WMI){
$disk = $Drive.Caption
$status = $Drive.Status
#condition will be changed to "-notmatch"
if ($status -match "OK"){
#I'm using write-output to see if the script works during testing
Write-output $computername $disk $status
}
}
}
workflow Get-disk-status {
param(
[string[]]$computers
)
foreach -parallel ($computer in $computers) {
disk-status -computername $computer
}
}
#in the final version I'm going to use get-adcomputer
$computers = "hostname1", "hostname2"
Get-disk-status $computers
Output I get:
hostname1
ST500LM0 21-1KJ152 SCSI Disk Device
OK
hostname2
ST500LM0 21-1KJ152 SCSI Disk Device
OK
Can anybody give me at least a hint how to fix it?
Thank you in advance!
Try changing
$WMI = Get-WMIObject -Class Win32_DiskDrive
to
$WMI = Get-WMIObject -Class Win32_DiskDrive -ComputerName $computername
It looks like it may be retrieving information from the machine you are on because you haven't passed a computer to the Get-WMIObject cmdlet.

cmdlets not recognized on the first run in powershell

I am facing an issue with the first run of powershell code.
cmdlets and user defined function are not recognized in the first run but works fine if I run the code again
user defined function takes values from previous run.i.e. basically we need to run the code twice to get the correct result
Code:
$resultVar=get-CPUAndMemUtilization -computername $computername -CPUCriteria $CPUCriteria -MemCriteria $MemCriteria
#Write-Host "Mme:"$resultVar;
$CPUMem += [PSCustomObject] #{
CPULoad = "$($resultVar[0])"
MemLoad = "$($resultVar[1])"
}
Write-Host $CPUMem;
function get-CPUAndMemUtilization($computername,$CPUCriteria,$MemCriteria)
{
$Memstatus=$null;
$CPUstatus=$null;
$AVGProc = Get-WmiObject -computername $computername win32_processor | Measure-Object -property LoadPercentage -Average | Select Average
$OS = gwmi -Class win32_operatingsystem -computername $computername |
Select-Object #{Name = "MemoryUsage"; Expression = {“{0:N2}” -f ((($_.TotalVisibleMemorySize - $_.FreePhysicalMemory)*100)/ $_.TotalVisibleMemorySize) }}
$result += [PSCustomObject] #{
ServerName = "$computername"
CPULoad = "$($AVGProc.Average)%"
MemLoad = "$($OS.MemoryUsage)%"
}
if($AVGProc.Average -lt $CPUCriteria)
{
$Memstatus=1;
}else{
$Memstatus=0;
}
if($OS.MemoryUsage -lt $MemCriteria)
{
$CPUstatus=1;
}else{
$CPUstatus=0;
}
$CPUstatus
$Memstatus
return;
}
Code return the System CPU & Me usage of the system in CPU & Mem utilization for a system
Error:
get-CPUAndMemUtilization : The term 'get-CPUAndMemUtilization' is not
recognized as the name of a cmdlet, function, script file, or operable
program. Check the spelling of the name, or if a path was included,
verify that the path is correct and try again.
You call the function before you import it (so it doesn't exist) into the powershell session, just swap those 2 things:
function get-CPUAndMemUtilization($computername,$CPUCriteria,$MemCriteria)
{
...
}
$resultVar=get-CPUAndMemUtilization -computername $computername -CPUCriteria $CPUCriteria -MemCriteria $MemCriteria
#Write-Host "Mme:"$resultVar;
$CPUMem += [PSCustomObject] #{
CPULoad = "$($resultVar[0])"
MemLoad = "$($resultVar[1])"
}
Write-Host $CPUMem;

Enable Silverlight Plugin (NPAPI) in Chrome using registry key fix

I have created a Powershell script to add the npapi and Silverlight* registry keys to enable Silverlight in Google Chrome. The Powershell script works fine and adds the two registry keys, however the Silverlight plugin is still disabled in Chrome and when I load any Silverlight based sites I get the “Install Silverlight” popup. I have restarted the machine and still the Silverlight plugin is disabled.
However, if I go into the registry and delete just the npapi and Silverlight* registry keys and re-create them (String value - REG_SZ), when I reload the page in Chrome, Silverlight is now enabled and the site loads perfectly. I don’t understand what’s going on.
The powershell script creates these keys but only when I delete them and re-create them manually do they take effect and the Silverlight plugin is enabled. Then if I go into chrome://plugins, Chrome reports that the Silverlight plugin is “Enabled by Enterprise policy”. I have also run the script on another machine and the exact same thing happens. Has anyone else experienced this and does anyone know the fix or what I am doing wrong?
Powershell Script used to create the npapi and Silverlight* registry keys:
function Create-Path {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $false)]
[switch]$OverwriteIfExists
)
process {
If(($OverwriteIfExists.IsPresent) -or (-not (Test-Path $Path))) {
New-Item $Path -Force | out-null
}
}
}
function Get-RegistryKeyWithValue {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
,
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
process {
$properties = Get-Item $Path | select -ExpandProperty Property
$properties | %{
$property = Get-ItemProperty -Path $Path -Name $_
if ($property.$_ -eq $Value) {
write-output $property
}
}
}
}
function Get-NextKeyInPath {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $false, Mandatory = $true)]
[string]$Path
)
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
}
function Create-ChromeEnabledPluginPolicy {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true, Mandatory = $true)]
[string]$Value
)
begin {
$ChromePluginPolicyPath = "HKLM:\SOFTWARE\Policies\Google\Chrome\EnabledPlugins"
Create-Path $ChromePluginPolicyPath
}
process {
if (-not (Get-RegistryKeyWithValue -Path $ChromePluginPolicyPath -Value $Value)) {
$keyName = Get-NextKeyInPath -Path $ChromePluginPolicyPath
New-ItemProperty -path $ChromePluginPolicyPath -Name $keyName -Value $Value -PropertyType String
}
}
}
"npapi", "Silverlight*" | Create-ChromeEnabledPluginPolicy
The code:
process {
try {
write-output ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1) | out-string
} catch {
write-output "1"
}
}
Seems to return something more than a single string.
Amending to the following resolves the issue:
process {
try {
[int]$i = ((Get-Item $Path -ErrorAction Stop | select -ExpandProperty Property | Measure-Object -Maximum).Maximum + 1)
write-output ([string]$i)
} catch {
write-output "1"
}
}
A simplified demo of the issue & solution:
Run the following code: New-ItemProperty -Path 'HKLM:\SOFTWARE\JohnLBevan' -Name (1 | out-string) -PropertyType String -Value 'test'
Now open regedit and create a key with name 1; it succeeds (i.e. you have two keys called 1; so clearly some control/non displayable character is being added in our PS script).
If you try to add a third key called 1 using either method (regedit or powershell) you'll get an error due to a key with that name already existing (showing that there is a unique check in place; it's just our original 1s aren't unique)
If you try either of the following code snippets, things work as expected:
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name "1" -PropertyType String -Value 'test'
New-ItemProperty -Path'HKLM:\SOFTWARE\JohnLBevan' -Name [string]1 -PropertyType String -Value 'test'
(Disclosure: I work with #jwoods83, so had the advantage of seeing the issue / playing with it directly)