Can anyone give some help with powershell tables?
The working part of the script
Function CheckWMI {
Param (
[Parameter(Mandatory=$True)]
$Computers
)
$CheckWMIResults = New-Object system.Data.DataTable
$Function = $CheckWMIResults.columns.add("ComputerName", [System.Type]::GetType("System.String") )
$Function = $CheckWMIResults.columns.add("Attempts", [System.Type]::GetType("System.Int32") )
$Function = $CheckWMIResults.columns.add("Result", [System.Type]::GetType("System.String") )
ForEach ($Computer in $Computers) {
$CheckWMIResults.Rows.Add($Computer,"0","Incomplete")
}
}
CheckWMI "192.168.1.8","192.168.1.7","192.168.1.6"
As you can see it takes each of the ip addresses and create a separate row for them.
Now how can I select one of those rows and update it, such as the count column of the second row?
There is no need to use a data structure so heavy as a DataTable for this. All you need is a simple collection like an array and the generic PSObject. The following rewrites your script above, then sets the Result of the first computer to Complete:
Function CheckWMI {
Param (
[Parameter(Mandatory=$True)]
[string[]]$Computers
)
$CheckWMIResults = #();
ForEach ($Computer in $Computers) {
$TempResults = New-Object PSObject;
$TempResults | Add-Member -MemberType NoteProperty -Name "ComputerName" -Value $Computer;
$TempResults | Add-Member -MemberType NoteProperty -Name "Attempts" -Value 0;
$TempResults | Add-Member -MemberType NoteProperty -Name "Result" -Value "Incomplete";
$CheckWMIResults += $TempResults;
}
$CheckWMIResults;
}
$Results = CheckWMI -Computers "192.168.1.8","192.168.1.7","192.168.1.6"
$Results[0].Result = "Complete";
$Results;
If you do need type checking (which the DataTable gives you), define your own type.
add-type #"
public class WMIResults {
public string ComputerName;
public int Attempts;
public string Result;
}
"#
Function CheckWMI {
Param (
[Parameter(Mandatory=$True)]
[string[]]$Computers
)
$CheckWMIResults = #();
ForEach ($Computer in $Computers) {
$TempResults = New-Object WMIResults;
$TempResults.ComputerName = $Computer
$TempResults.Attempts = 0;
$TempResults.Result = "Incomplete";
$CheckWMIResults += $TempResults;
}
$CheckWMIResults;
}
$Results = CheckWMI -Computers "192.168.1.8","192.168.1.7","192.168.1.6"
$Results[0].Result = "Complete";
$Results;
See http://blogs.msdn.com/b/powershell/archive/2009/03/11/how-to-create-an-object-in-powershell.aspx and Get-Help Add-Type for more details on this second method ( you could use a struct instead of a class for trivial cases, but classes are generally a better idea).
Related
I'm unable to display the calculated property column called Logon Type which is translated from a hash table.
The script below is working fine, but I just need to translate the raw value number into a more meaningful description.
function Get-LogonEvents {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('ServerName', 'Server', 'Name')]
[string[]]$ComputerName,
[Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
[PSCredential]$Credential,
[Parameter()]
[ValidateSet("Service", "Interactive", "RemoteInteractive", "NetworkCleartext", "CachedInteractive", "Unlock", "NewCredentials", "Network", "*")]
[string[]]$LogonType = #("Interactive", "RemoteInteractive", "CachedInteractive"),
[string]$UserName,
[Parameter()]
[switch]$Oldest,
[Parameter()]
[int64]$MaxEvents,
[Parameter()]
[datetime]$StartTime = (Get-Date 1/1/1900),
[Parameter()]
[datetime]$StopTime = (Get-Date 1/1/2100)
)
Begin {
Function ParseEventMessage {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true)]
$obj
)
Begin {
$defaultDisplaySet = 'TimeCreated', 'MachineName', 'TargetDomainName', 'TargetUserName'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet(‘DefaultDisplayPropertySet’, [string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]#($defaultDisplayPropertySet)
$myHash = #{ }
}
Process {
([xml]($obj.ToXml())).event.eventdata.data | ForEach-Object { $myHash[$PSItem.name] = $PSItem.'#text' }
New-Object -TypeName PSObject -Property $myHash | ForEach-Object {
$PSItem.PSObject.TypeNames.Insert(0, "EventLogRecord.XMLParse")
$PSItem | Add-Member MemberSet PSStandardMembers $PSStandardMembers -PassThru |
Add-Member -MemberType NoteProperty -Name TimeCreated -Value $obj.timecreated -PassThru |
Add-Member -MemberType NoteProperty -Name MachineName -Value $obj.MachineName -PassThru
}
}
}
$hashLogonType = #{
"Interactive" = "2"
"Network" = "3"
"Service" = "5"
"Unlock" = "7"
"NetworkCleartext" = "8"
"NewCredentials" = "9"
"RemoteInteractive" = "10"
"CachedInteractive" = "11"
}
$filter = #"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[
(EventID='4624')
and TimeCreated[#SystemTime>='{0}' and #SystemTime<='{1}']
]
and EventData[
Data[#Name='LogonType'] and ({2})
{3}
]
]
</Select>
</Query>
</QueryList>
"#
}
Process {
foreach ($obj in $ComputerName) {
if ($UserName) {
$joinUserName = "and Data[#Name='TargetuserName'] and (Data='{0}')" -f $UserName
}
$joinLogonType = ($LogonType | ForEach-Object { $hashLogonType[$PSItem] }) -replace '^', "Data='" -replace '$', "'" -join " or "
$objFilter = $filter -f (Get-Date $StartTime -Format s), (Get-Date $StopTime -Format s), $joinLogonType, $joinUserName
$hashEventParm = #{
ComputerName = $obj
FilterXml = $objFilter
}
if ($Credential) { $hashEventParm['Credential'] = $Credential }
if ($MaxEvents) { $hashEventParm['MaxEvents'] = $MaxEvents }
$objFilter | Write-Verbose
Get-WinEvent #hashEventParm | ParseEventMessage
}
}
End { }
}
$TargetDomainNameException = #('Window Manager','Font Driver Host')
$exceptionRegex = $TargetDomainNameException -join "|"
Get-LogonEvents -ComputerName 'Localhost' -MaxEvents 10 |
Where-Object { ($_.TargetDomainName -notmatch $exceptionRegex) } |
Select-Object WorkstationName,
TargetUserName,
TargetDomainName,
Type,
LogonType,
#{n ='LogonType'; e={$hashLogonType[[string]$_.LogonType]}},
#{n = 'Logon Type'; e = {$hashLogonType["$($_.LogonType)"]}},
ProcessName,
IPAddress,
#{n="Host Name"; e={([System.Net.Dns]::GetHostByAddress($_.IPaddress).Hostname)}},
TimeCreated |
Out-GridView
Error:
I have modifiedthe Calculated property like:
#{n = 'Logon Type'; e = {$hashLogonType["$($_.LogonType)"]}},
Somehow it is still not displaying the column "Logon Type", however, the raw value on LogonType column still showing as 10, 3 ,etc...?
I see two problems.
$hashLogonType is defined inside the function and won't be available in the global scope.
The keys for $hashLogonType are by [string] not by [int].
If you're able to modify the original function, you might consider adding a property where the string value of LogonType is saved.
Otherwise, keep a copy of $hashLogonType in your variable scope with integers as keys, and base your calculated property on that.
The easiest way to get what you want is to create your own hash table and use it in your pipeline.
# Create a hash table for your own use in your variable scope.
$myHashTable = #{
2 = "Interactive"
3 = "Network"
5 = "Service"
7 = "Unlock"
8 = "NetworkCleartext"
9 = "NewCredentials"
10 = "RemoteInteractive"
11 = "CachedInteractive"
}
# Shim object.
$exampleObject = [PSCustomObject]#{
LogonType = 2
WorkstationName = "myHost.example.com"
}
# Modify your pipeline to use your hash table.
$exampleObject |
Select-Object -Property WorkstationName, LogonType, #{label="Logon Title";expression={$myHashTable[$_.LogonType]}}
PS> ./Answer 02.ps1
WorkstationName LogonType Logon Title
--------------- --------- -----------
myHost.example.com 2 Interactive
In principle, it is possible to modify the original function. But, I don't have any data to test with. Maybe Doug can help. He seems to have access to an event log.
You would have to do two things.
Add a hash table with integer keys in scope for ParseEventMessage(). For example, add the hash table to ParseEventMessage()'s Begin block.
Where it says
Add-Member -MemberType NoteProperty -Name MachineName -Value $obj.MachineName -PassThru
Add another property by extending that pipeline:
Add-Member -MemberType NoteProperty -Name LogonTitle -Value {$myHashTable[$_.LogonType]} -PassThru
Edit: Yes Mike is absolutely correct, the hashtable was defined inside the get-logonevents function and not used. I've moved it out and now it should work.
I think you should reverse the assignment of the hashtable. Either as an int or a string should work then. I did it like this and it worked fine.
function Get-LogonEvents {
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('ServerName', 'Server', 'Name')]
[string[]]$ComputerName,
[Parameter(ValueFromPipelineByPropertyName = $true, Mandatory = $true)]
[PSCredential]$Credential,
[Parameter()]
[ValidateSet("Service", "Interactive", "RemoteInteractive", "NetworkCleartext", "CachedInteractive", "Unlock", "NewCredentials", "Network", "*")]
[string[]]$LogonType = #("Interactive", "RemoteInteractive", "CachedInteractive"),
[string]$UserName,
[Parameter()]
[switch]$Oldest,
[Parameter()]
[int64]$MaxEvents,
[Parameter()]
[datetime]$StartTime = (Get-Date 1/1/1900),
[Parameter()]
[datetime]$StopTime = (Get-Date 1/1/2100)
)
Begin {
Function ParseEventMessage {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline = $true)]
$obj
)
Begin {
$defaultDisplaySet = 'TimeCreated', 'MachineName', 'TargetDomainName', 'TargetUserName'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet(‘DefaultDisplayPropertySet’, [string[]]$defaultDisplaySet)
$PSStandardMembers = [System.Management.Automation.PSMemberInfo[]]#($defaultDisplayPropertySet)
$myHash = #{ }
}
Process {
([xml]($obj.ToXml())).event.eventdata.data | ForEach-Object { $myHash[$PSItem.name] = $PSItem.'#text' }
New-Object -TypeName PSObject -Property $myHash | ForEach-Object {
$PSItem.PSObject.TypeNames.Insert(0, "EventLogRecord.XMLParse")
$PSItem | Add-Member MemberSet PSStandardMembers $PSStandardMembers -PassThru |
Add-Member -MemberType NoteProperty -Name TimeCreated -Value $obj.timecreated -PassThru |
Add-Member -MemberType NoteProperty -Name MachineName -Value $obj.MachineName -PassThru
}
}
}
$filter = #"
<QueryList>
<Query Id="0" Path="Security">
<Select Path="Security">
*[System[
(EventID='4624')
and TimeCreated[#SystemTime>='{0}' and #SystemTime<='{1}']
]
and EventData[
Data[#Name='LogonType'] and ({2})
{3}
]
]
</Select>
</Query>
</QueryList>
"#
}
Process {
foreach ($obj in $ComputerName) {
if ($UserName) {
$joinUserName = "and Data[#Name='TargetuserName'] and (Data='{0}')" -f $UserName
}
$joinLogonType = ($LogonType | ForEach-Object { $hashLogonType[$PSItem] }) -replace '^', "Data='" -replace '$', "'" -join " or "
$objFilter = $filter -f (Get-Date $StartTime -Format s), (Get-Date $StopTime -Format s), $joinLogonType, $joinUserName
$hashEventParm = #{
ComputerName = $obj
FilterXml = $objFilter
}
if ($Credential) { $hashEventParm['Credential'] = $Credential }
if ($MaxEvents) { $hashEventParm['MaxEvents'] = $MaxEvents }
$objFilter | Write-Verbose
Get-WinEvent #hashEventParm | ParseEventMessage
}
}
End { }
}
$hashLogonType = #{
2 = "Interactive"
3 = "Network"
5 = "Service"
7 = "Unlock"
8 = "NetworkCleartext"
9 = "NewCredentials"
10 = "RemoteInteractive"
11 = "CachedInteractive"
}
$TargetDomainNameException = #('Window Manager','Font Driver Host')
$exceptionRegex = $TargetDomainNameException -join "|"
Get-LogonEvents -ComputerName 'Localhost' -MaxEvents 10 -OutVariable LogonEvents |
Where-Object { ($_.TargetDomainName -notmatch $exceptionRegex) } |
Select-Object WorkstationName,
TargetUserName,
TargetDomainName,
Type,
#{n="LogonType";e={$hashLogonType.[int]$_.logontype}},
ProcessName,
IPAddress,
#{n="Host Name"; e={([System.Net.Dns]::GetHostByAddress($_.IPaddress).Hostname)}},
TimeCreated |
Out-GridView
I am building a multi-step local search engine with powershell that also allows you to email selected pieces of information.
I have got the search engine part down and the email part down, I just need to get the select part down.
So right now, you open the program and it prompts you to search for what you want. If I put in the query when, this is what is returned:
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 1/25/2017 3:29 PM 8357890 01 - Kiss Me When I'm Down.mp3
-a--- 1/24/2017 2:15 PM 7189290 09 - When You Love Someone.mp3
Now the goal is to select let's say 01 - Kiss Me When I'm Down.mp3, because I'm then going to put that into an $attachment variable, which will then send the song as an attachment. Is this achievable?
EDIT for clarity:
I've tried working with Select-Object to do this, but I can't get it to allow the user to select the song that they want. That is the goal of this, to allow the user to select the input they want.
This is an inelegant solution that adds an Index NoteProperty using the Add-Member cmdlet. As an example I used Get-ChildItem results:
$Items = Get-ChildItem C:\
$Index = 1
$Count = $Items.Count
foreach ($Item in $Items) {
$Item | Add-Member -MemberType NoteProperty -Name "Index" -Value $Index
$Index++
}
$Items | Select-Object Index, Attributes, LastWriteTime, Name | Out-Host
$Input = Read-Host "Select an item by index (1 to $Count)"
$Selected = $Items[$Input - 1]
Write-Host "You have selected $Selected"
I realise some good answers have been given, but the OP's post got me thinking about extracting the meta data for the MP3s...:
function getMP3Details() {
param ( [System.IO.FileInfo] $mp3file = $null )
[System.__ComObject] $Local:objShell = $null;
[System.__ComObject] $Local:objFolder = $null;
[System.__ComObject] $Local:objFile = $null;
[HashTable] $Local:objTags = #{ 0 = 'Name'; 1 = 'Size'; 13 = 'Artists'; 14 = 'Album'; 15 = 'Year'; 16 = 'Genre'; 20 = 'Authors'; 21 = 'Title'; 28 = 'Bit Rate'; }
[Int32] $Local:intTagIndex = 0;
[String] $Local:strTagName = '';
[String] $Local:strTagValue = '';
[PSCustomObject] $Local:objOutput = $null;
try {
if ( $mp3file -ne $null ) {
$objShell = New-Object -ComObject Shell.Application;
$objFolder = $objShell.NameSpace( $mp3file.DirectoryName );
$objFile = $objFolder.ParseName( $mp3file.Name );
$objOutput = New-Object -TypeName PSCustomObject;
foreach ( $intTagIndex in ($objTags.Keys | Sort-Object) ) {
$strTagName = $objTags[$intTagIndex];
$strTagValue = $objFolder.GetDetailsOf( $objFile, $intTagIndex );
Add-Member -InputObject $objOutput -MemberType NoteProperty -Name $strTagName -Value ( [String] ($strTagValue -replace '[^ -~]', '') );
} #foreach
Write-Output -InputObject $objOutput;
} #if
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
return;
}
[String] $Local:strFolder = '<PATH TO ALBUM>';
[PSCustomObject[]] $Local:arrMP3Tracks = #();
[PSCustomObject] $Local:objSelectedTrack = $null;
try {
Get-ChildItem -LiteralPath $strFolder -File -Filter *.mp3 | Foreach-Object {
$arrMP3Tracks += getMP3Details -mp3file $_;
} #Foreach-Object
$objSelectedTrack = $arrMP3Tracks | Out-GridView -PassThru;
} #try
catch [System.Exception] {
# Do something.
'Error';
} #catch
exit 0;
Goal
I'm looking to utilize the new Class feature of PowerShell 5 to create a class that i can use within my project to view records of a database and create methods to link and unlink foreign keys together.
The below class is working with 2 properties but i need to add more and don't want to copy/paste, this is how it's currently working:
# Create a new instance of my Computer class
$computer = [Computer]::new([int]71)
# Change the status of the Server in the DataRow
$computer.Status = "Active"
# Update the Row in the Database
$computer.SaveChanges()
# Link this Server in the Database to a record in the Locations table
$computer.LinkLocation([int]16)
I've got the Class part and even a few Methods implemented but i'm wondering if i can reduce the amount of code needed in the class by somehow referencing the Name property of a ScriptProperty.
Class
class Computer
{
hidden [System.Data.DataRow]$_dataRow
hidden [System.Data.SqlClient.SqlDataAdapter]$_dataAdapter
hidden GetComputerDetails([int]$serverId)
{
$connectionString = "Server=HomeTestServer\DB01;Database=TestDB;Integrated Security=SSPI;"
$sqlQuery = "SELECT ComputerName, Status FROM tbComputers WHERE Id = $serverId"
$sqlConnection = New-Object -TypeName System.Data.SqlClient.SqlConnection -ArgumentList $connectionString
$sqlCommand = New-Object -TypeName System.Data.SqlClient.SqlCommand -ArgumentList $SqlQuery, $sqlConnection
$sqlConnection.Open()
$sqlAdapter = New-Object -TypeName System.Data.SqlClient.SqlDataAdapter -ArgumentList $sqlCommand
$sqlData = New-Object -TypeName System.Data.DataSet
$sqlBuilder = New-Object -TypeName System.Data.SqlClient.SqlCommandBuilder -ArgumentList $sqlAdapter
$sqlAdapter.Fill($sqlData, "Computer")
$this._dataRow = $sqlData.Tables[0].Rows[0]
$this._dataAdapter = $sqlAdapter
}
hidden [object] GetValue([string]$propertyName)
{
return $this._dataRow.$propertyName
}
hidden SetValue([string]$propertyName, $value)
{
$this._dataRow.$propertyName = $value
}
SaveChanges()
{
$this._dataAdapter.Update($this._dataRow)
}
LinkLocation([int]$locationId)
{
$serverId = $this._dataRow.Id
[void](Invoke-Sqlcmd -ServerInstance "HomeTestServer\DB01" -Database TestDB -Query "UPDATE tbComputers SET LocationId = $locationId WHERE Id = $serverId" )
}
Computer([int]$serverId)
{
$this.GetComputerDetails([int]$serverId)
$this | Add-Member -MemberType ScriptProperty -Name ComputerName -Force -Value `
{
# Is there a way to get the ScriptProperty's Name?
$this.GetValue("ComputerName")
} `
{
param
(
$value
)
# Is there a way to get the ScriptProperty's Name?
$this.SetValue("ComputerName", $value)
}
$this | Add-Member -MemberType ScriptProperty -Name Status -Force -Value `
{
# Is there a way to get the ScriptProperty's Name?
$this.GetValue("Status")
} `
{
param
(
$value
)
# Is there a way to get the ScriptProperty's Name?
$this.SetValue("Status", $value)
}
}
}
Explanation
This class essentially wraps the DataRow and DataAdapter for easy record viewing and updating as the ScriptProperty's modify the values directly on the DataRow and the Savechanges method updates the DataRow using the DataAdapter
Problem
For each property that i want to wrap in my Class, i need to make a ScriptProperty with a Getter and a Setter, the above Class has 2 properties and doesn't look to bad but what if my SQL Table had 40 properties? I really do not want to copy and paste the $this | Add-Member... line 40 times.
Ideally i'm looking for a way to loop through and create each ScriptProperty dynamically
Attempts
foreach($propertyName in $this._dataRow.Table.Columns)
{
# Looping through doesn't work, my working theory is the Get/Set block don't expand the variable when the Member is being added, only when it's being called
$this | Add-Member -MemberType ScriptProperty -Name $propertyName -Force -Value `
{
# This is the Get block
$this.GetValue($propertyName)
} `
{
param
(
$value
)
# This is the Set block
$this.SetValue($propertyName, $value)
}
}
Edit
Highly simplified example:
class Computer
{
hidden [int]$number3 = 4
hidden [int]$number2 = 13
hidden ExtractFields([string]$propertyName)
{
$this | Add-Member -MemberType ScriptProperty -Name $propertyName -Force -Value `
{
$this.GetValue($propertyName)
} `
{
param
(
$value
)
$this.SetValue($propertyName,$value)
}
}
hidden [object] GetValue($propertyName)
{
return $this.$propertyName
}
hidden SetValue($propertyName, $value)
{
$this.$propertyName = $value
}
Computer()
{
foreach($property in #('number2', 'number3'))
{
$this.ExtractFields($property)
}
}
}
Error recieved when trying to set a dynamic property (e.g `$computer.number2 = 17)
Exception setting "number2": "The property '' cannot be found on this object. Verify that the property exists and can be set."
At line:1 char:1
+ $computer.number2 = 17
+ ~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], SetValueInvocationException
+ FullyQualifiedErrorId : ScriptSetValueRuntimeException
Question
Is there a way i can dynamically create ScriptProperty's based on specified property name?
This can be done with dynamic generation of scriptblocks for the setters and getters. Suppose class Bar is defined with two methods that implement a general way to access your fields:
class Bar
{
[object] GetFieldValue ( [string] $FieldName )
{
# replace this with code that looks up your value by field name
Write-Host "Invoked GetFieldValue($FieldName)"
return "value of $FieldName"
}
SetFieldValue ( [string] $FieldName, [object] $Value )
{
# replace this with code that sets your field value
Write-Host "Invoked SetFieldValue($FieldName,$Value)"
}
}
A function can then be implemented that decorates a Bar object with dynamically-generated properties that have getters and setters that invoke those methods:
function New-Bar
{
param( [string[]]$FieldNames )
# create the new object
$outputObject = [Bar]::new()
# add a property for each field
foreach ( $fieldName in $FieldNames )
{
$getter = [scriptblock]::Create(
"`$this.GetFieldValue('$fieldName')"
)
$setter = [scriptblock]::Create(
"`param(`$p) `$this.SetFieldValue('$fieldName',`$p)"
)
$outputObject |
Add-Member ScriptProperty $fieldName $getter $setter -Force
}
# return the object
$outputObject
}
Fields can then be accessed like this:
$b = New-Bar 'field1','field2'
$b.field1 = 'my value for field 1'
$b.field1
Which outputs:
Invoked GetFieldValue(field1)
value of field1
Invoked SetFieldValue(field1,my value for field 1)
I am implementing a custom cloudwatch metric for a windows ec2 instance and here's my script for the same. This metric should report cloudwatch regarding the memory and pagefile details.
[CmdletBinding(DefaultParametersetName="credsfromfile", supportsshouldprocess = $true) ]
param(
[switch]$mem_util,
[switch]$mem_used ,
[switch]$mem_avail,
[switch]$page_used,
[switch]$page_avail,
[switch]$page_util,
[switch]$auto_scale,
[ValidateSet("bytes","kilobytes","megabytes","gigabytes" )]
[string]$memory_units = "none",
[switch]$from_scheduler,
[Parameter(Parametersetname ="credsinline",mandatory=$true)]
[string]$aws_access_id = "",
[Parameter(Parametersetname ="credsinline",mandatory=$true)]
[string]$aws_secret_key = "",
[Parameter(Parametersetname ="credsfromfile")]
[string]$aws_credential_file = [Environment]::GetEnvironmentVariable("AWS_CREDENTIAL_FILE"),
[string]$logfile = $null,
[Switch]$version
)
$ErrorActionPreference = 'Stop'
### Initliaze common variables ###
$accountinfo = New-Object psobject
$wc = New-Object Net.WebClient
$time = Get-Date
[string]$aaid =""
[string]$ask =""
$invoc = (Get-Variable myinvocation -Scope 0).value
$currdirectory = Split-Path $invoc.mycommand.path
$scriptname = $invoc.mycommand.Name
$ver = '1.0.0'
$client_name = 'CloudWatch-PutInstanceDataWindows'
$useragent = "$client_name/$ver"
### Logs all messages to file or prints to console based on from_scheduler setting. ###
function report_message ([string]$message)
{
if($from_scheduler)
{ if ($logfile.Length -eq 0 )
{
$logfile = $currdirectory +"\" +$scriptname.replace('.ps1','.log')
}
$message | Out-File -Append -FilePath $logfile
}
else
{
Write-Host $message
}
}
### Global trap for all exceptions for this script. All exceptions will exit the script.###
trap [Exception] {
report_message ($_.Exception.Message)
Exit
}
if ($version)
{
report_message "$scriptname version $ver"
exit
}
####Test and load AWS sdk
$ProgFilesLoc = (${env:ProgramFiles(x86)}, ${env:ProgramFiles} -ne $null)[0]
$SDKLoc = "$ProgFilesLoc\AWS SDK for .NET\bin\Net35"
if ((Test-Path -PathType Container -Path $SDKLoc) -eq $false) {
$SDKLoc = "C:\Windows\Assembly"
}
$SDKLibraryLocation = dir C:\Windows\Assembly -Recurse -Filter "AWSSDK.dll"
if ($SDKLibraryLocation -eq $null)
{
throw "Please Install .NET sdk for this script to work."
}
else
{
$SDKLibraryLocation = $SDKLibraryLocation.FullName
Add-Type -Path $SDKLibraryLocation
Write-Verbose "Assembly Loaded"
}
### Process parameterset for credentials and adds them to a powershell object ###
switch ($PSCmdlet.Parametersetname)
{
"credsinline" {
Write-Verbose "Using credentials passed as arguments"
if (!($aws_access_id.Length -eq 0 ))
{
$aaid = $aws_access_id
}
else
{
throw ("Value of AWS access key id is not specified.")
}
if (!($aws_secret_key.Length -eq 0 ))
{
$ask = $aws_secret_key
}
else
{
throw "Value of AWS secret key is not specified."
}
}
"credsfromfile"{
if ( Test-Path $aws_credential_file)
{
Write-Verbose "Using AWS credentials file $aws_credential_file"
Get-Content $aws_credential_file | ForEach-Object {
if($_ -match '.*=.*'){$text = $_.split("=");
switch ($text[0].trim())
{
"AWSAccessKeyId" {$aaid= $text[1].trim()}
"AWSSecretKey" { $ask = $text[1].trim()}
}}}
}
else {throw "Failed to open AWS credentials file $aws_credential_file"}
}
}
if (($aaid.length -eq 0) -or ($ask.length -eq 0))
{
throw "Provided incomplete AWS credential set"
}
else
{
Add-Member -membertype noteproperty -inputobject $accountinfo -name "AWSSecretKey" -value $ask
Add-Member -membertype noteproperty -inputobject $accountinfo -name "AWSAccessKeyId" -value $aaid
Remove-Variable ask; Remove-Variable aaid
}
### Check if atleast one metric is requested to report.###
if ( !$mem_avail -and !$mem_used -and !$mem_util -and !$page_avail -and !$page_used -and !$page_util)
{
throw "Please specify a metric to report exiting script"
}
### Avoid a storm of calls at the beginning of a minute.###
if ($from_scheduler)
{
$rand = new-object system.random
start-sleep -Seconds $rand.Next(20)
}
### Functions that interact with metadata to get data required for dimenstion calculation and endpoint for cloudwatch api. ###
function get-metadata {
$extendurl = $args
$baseurl = "http://169.254.169.254/latest/meta-data"
$fullurl = $baseurl + $extendurl
return ($wc.DownloadString($fullurl))
}
function get-region {
$az = get-metadata("/placement/availability-zone")
return ($az.Substring(0, ($az.Length -1)))
}
function get-endpoint {
$region = get-region
return "https://monitoring." + $region + ".amazonaws.com/"
}
### Function that creates metric data which will be added to metric list that will be finally pushed to cloudwatch. ###
function append_metric {
$metricdata = New-Object Amazon.Cloudwatch.Model.MetricDatum
$metricdata.metricname, $metricdata.Unit, $metricdata.value, $metricdata.Dimensions = $args
$metricdata.Timestamp = $time.ToUniversalTime()
return $metricdata
}
### Function that validates units passed. Default value of Megabytes is used###
function parse-units {
param ([string]$mem_units,
[long]$mem_unit_div)
$units = New-Object psobject
switch ($memory_units.ToLower())
{
"bytes" { $mem_units = "Bytes"; $mem_unit_div = 1}
"kilobytes" { $mem_units = "Kilobytes"; $mem_unit_div = 1kb}
"megabytes" { $mem_units = "Megabytes"; $mem_unit_div = 1mb}
"gigabytes" { $mem_units = "Gigabytes"; $mem_unit_div = 1gb}
default { $mem_units = "Megabytes"; $mem_unit_div = 1mb}
}
Add-Member -InputObject $units -Name "mem_units" -MemberType NoteProperty -Value $mem_units
Add-Member -InputObject $units -Name "mem_unit_div" -MemberType NoteProperty -Value $mem_unit_div
return $units
}
### Function that gets memory stats using WMI###
function get-memory {
begin {}
process {
$mem = New-Object psobject
$units = parse-units
[long]$mem_avail_wmi = (get-WmiObject Win32_OperatingSystem | select -expandproperty FreePhysicalMemory) * 1kb
[long]$total_phy_mem_wmi = get-WmiObject Win32_ComputerSystem | select -expandproperty TotalPhysicalMemory
[long]$mem_used_wmi = $total_phy_mem_wmi - $mem_avail_wmi
Add-Member -InputObject $mem -Name "mem_avail_wmi" -MemberType NoteProperty -Value $mem_avail_wmi
Add-Member -InputObject $mem -Name "total_phy_mem_wmi" -MemberType NoteProperty -Value $total_phy_mem_wmi
Add-Member -InputObject $mem -Name "mem_used_wmi" -MemberType NoteProperty -Value $mem_used_wmi
Add-Member -InputObject $mem -Name "mem_units" -MemberType NoteProperty -Value $units.mem_units
Add-Member -InputObject $mem -Name "mem_unit_div" -MemberType NoteProperty -Value $units.mem_unit_div
write $mem
}
end{}
}
### Function that writes metrics to be piped to next fucntion to push to cloudwatch.###
function create-metriclist {
param (
[parameter(Valuefrompipeline=$true)] $mem_info)
begin{
$group_name = ""
$instance_id = get-metadata("/instance-id")
$auto_scale_group = Get-ASAutoScalingGroup
foreach($as in $auto_scale_group)
{
$flag = 0
$group_name = $as.AutoScalingGroupName
foreach($ins in $as.Instances)
{
Write-Host $ins.InstanceId
Write-Host "`r`n"
if ($ins.InstanceId -eq $instance_id)
{
$flag = 1
break
}
}
if ($flag -eq 1)
{
break
}
Write-Host "---------------------------`r`n"
}
Write-Host $instance_id
Write-Host "`r`n"
Write-Host $group_name
Write-Host "`r`n"
$dimlist = New-Object Collections.Generic.List[Amazon.Cloudwatch.Model.Dimension]
$dims = New-Object Amazon.Cloudwatch.Model.Dimension
if ( $auto_scale)
{
$dims.Name = "AutoScalingGroupName"
$dims.value = $group_name
}
else
{
$dims.Name = "InstanceId"
$dims.value = $instance_id
}
$dimlist.Add($dims)
$pagefilessize = #{}
$pagefileusage = #{}
gwmi Win32_PageFileSetting | ForEach-Object{$pagefilessize[$_.name]=$_.MaximumSize *1mb}
gwmi Win32_PageFileUsage | ForEach-Object{$pagefileusage[$_.name]=$_.currentusage *1mb}
[string[]]$pagefiles = $pagefilessize.keys
}
process{
if ($mem_util)
{
$percent_mem_util= 0
if ( [long]$mem_info.total_phy_mem_wmi -gt 0 ) { $percent_mem_util = 100 * ([long]$mem_info.mem_used_wmi/[long]$mem_info.total_phy_mem_wmi)}
write (append_metric "MemoryUtilization" "Percent" ("{0:N2}" -f $percent_mem_util) $dimlist)
}
if ($mem_used)
{
write (append_metric "MemoryUsed" $mem_info.mem_units ("{0:N2}" -f ([long]($mem_info.mem_used_wmi/$mem_info.mem_unit_div))) $dimlist)
}
if ( $mem_avail)
{
write (append_metric "MemoryAvailable" $mem_info.mem_units ("{0:N2}" -f ([long]($mem_info.mem_avail_wmi/$mem_info.mem_unit_div))) $dimlist)
}
if ($page_avail)
{
for ($i=0; $i -le ($pagefiles.count - 1);$i++)
{
write (append_metric ("pagefileAvailable("+$pagefiles[$i]+")") $mem_info.mem_units ("{0:N2}" -f ((($pagefilessize[$pagefiles[$i]]- ($pagefileusage[$pagefiles[$i]]))/$mem_info.mem_unit_div))) $dimlist)
}
}
if ($page_used)
{
for ($i = 0; $i -le ($pagefiles.count -1); $i++)
{
write (append_metric ("pagefileUsed("+$pagefiles[$i]+")") $mem_info.mem_units ("{0:N2}" -f (($pagefileusage[$pagefiles[$i]])/$mem_info.mem_unit_div)) $dimlist)
}
}
if ($page_util)
{
for ($i=0; $i -le ($pagefiles.count -1);$i++)
{
if($pagefilessize[$pagefiles[$i]] -gt 0 )
{
write (append_metric ("pagefileUtilization("+$pagefiles[$i]+")") "Percent" ("{0:N2}" -f((($pagefileusage[$pagefiles[$i]])*100)/$pagefilessize[$pagefiles[$i]])) $dimlist)
}
}
}
}
end{}
}
### Uses AWS sdk to push metrics to cloudwathc. This finally prints a requestid.###
function put-instancemem {
param (
[parameter(Valuefrompipeline=$true)] $metlist)
begin{
$cwconfig = New-Object Amazon.CloudWatch.AmazonCloudWatchConfig
$cwconfig.serviceURL = get-endpoint
$cwconfig.UserAgent = $useragent
$monputrequest = new-object Amazon.Cloudwatch.Model.PutMetricDataRequest
$response = New-Object psobject
$metricdatalist = New-Object Collections.Generic.List[Amazon.Cloudwatch.Model.MetricDatum]
}
process{
if ($PSCmdlet.shouldprocess($metlist.metricname,"The metric data "+$metlist.value.tostring() +" "+ $metlist.unit.tostring()+" will be pushed to cloudwatch")){
$metricdatalist.add($metlist)
Write-Verbose ("Metricname= " +$metlist.metricname+" Metric Value= "+ $metlist.value.tostring()+" Metric Units= "+$metlist.unit.tostring())
}
}
end{
$monputrequest.namespace = "System/Windows"
if ($metricdatalist.count -gt 0 ) {
$cwclient = New-Object Amazon.Cloudwatch.AmazonCloudWatchClient($accountinfo.AWSAccessKeyId,$accountinfo.AWSSecretKey,$cwconfig)
$monputrequest.metricdata = $metricdatalist
$monresp = $cwclient.PutMetricData($monputrequest)
Add-Member -Name "RequestId" -MemberType NoteProperty -Value $monresp.ResponseMetadata.RequestId -InputObject $response
}
else {throw "No metric data to push to CloudWatch exiting script" }
Write-Verbose ("RequestID: " + $response.RequestId)
}
}
### Pipelined call of fucntions that pushs metrics to cloudwatch.
get-memory | create-metriclist | put-instancemem
When I execute this manually, I am getting an output as shown below:
'UserAgent' is a ReadOnly property.
But when I try get-memory | create-metriclist it is giving me the full details of memory and pagefile. Which means something is going wrong while trying to push to cloudwatch.
Can someone help me?
I'm using PSVersion 2.0 and I was wondering is there a equivalent to the traceroute for it?
I'm aware that on PowerShell v4 there is Test-NetConnection cmdlet to do tracert but v2?! It can be done like:
Test-NetConnection "IPaddress/HOSTaname" -TraceRoute
Thanks
As mentioned in the comment, you can make your own "poor-mans-PowerShell-tracert" by parsing the output from tracert.exe:
function Invoke-Tracert {
param([string]$RemoteHost)
tracert $RemoteHost |ForEach-Object{
if($_.Trim() -match "Tracing route to .*") {
Write-Host $_ -ForegroundColor Green
} elseif ($_.Trim() -match "^\d{1,2}\s+") {
$n,$a1,$a2,$a3,$target,$null = $_.Trim()-split"\s{2,}"
$Properties = #{
Hop = $n;
First = $a1;
Second = $a2;
Third = $a3;
Node = $target
}
New-Object psobject -Property $Properties
}
}
}
By default, powershell formats objects with 5 or more properties in a list, but you can get a tracert-like output with Format-Table:
Fixed a few bugs in " Mid-Waged-Mans-Tracert" Version, modularized it, and added some customization pieces. #MrPaulch had a great PoC.
function Invoke-Traceroute{
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true,Position=1)]
[string]$Destination,
[Parameter(Mandatory=$false)]
[int]$MaxTTL=16,
[Parameter(Mandatory=$false)]
[bool]$Fragmentation=$false,
[Parameter(Mandatory=$false)]
[bool]$VerboseOutput=$true,
[Parameter(Mandatory=$false)]
[int]$Timeout=5000
)
$ping = new-object System.Net.NetworkInformation.Ping
$success = [System.Net.NetworkInformation.IPStatus]::Success
$results = #()
if($VerboseOutput){Write-Host "Tracing to $Destination"}
for ($i=1; $i -le $MaxTTL; $i++) {
$popt = new-object System.Net.NetworkInformation.PingOptions($i, $Fragmentation)
$reply = $ping.Send($Destination, $Timeout, [System.Text.Encoding]::Default.GetBytes("MESSAGE"), $popt)
$addr = $reply.Address
try{$dns = [System.Net.Dns]::GetHostByAddress($addr)}
catch{$dns = "-"}
$name = $dns.HostName
$obj = New-Object -TypeName PSObject
$obj | Add-Member -MemberType NoteProperty -Name hop -Value $i
$obj | Add-Member -MemberType NoteProperty -Name address -Value $addr
$obj | Add-Member -MemberType NoteProperty -Name dns_name -Value $name
$obj | Add-Member -MemberType NoteProperty -Name latency -Value $reply.RoundTripTime
if($VerboseOutput){Write-Host "Hop: $i`t= $addr`t($name)"}
$results += $obj
if($reply.Status -eq $success){break}
}
Return $results
}
I must admit I wanted to see whether someone already did this.
You can use the .Net Framework to implement a not-so-poor-mans-traceroute as a Powershell Script
Here a primer, that works fast, but dangerous.
Also, no statistics.
#
# Mid-Waged-Mans-Tracert
#
$ping = new-object System.Net.NetworkInformation.Ping
$timeout = 5000
$maxttl = 64
$address = [string]$args
$message = [System.Text.Encoding]::Default.GetBytes("MESSAGE")
$dontfragment = false
$success = [System.Net.NetworkInformation.IPStatus]::Success
echo "Tracing $address"
for ($ttl=1;$i -le $maxttl; $ttl++) {
$popt = new-object System.Net.NetworkInformation.PingOptions($ttl, $dontfragment)
$reply = $ping.Send($address, $timeout, $message, $popt)
$addr = $reply.Address
$rtt = $reply.RoundtripTime
try {
$dns = [System.Net.Dns]::GetHostByAddress($addr)
} catch {
$dns = "-"
}
$name = $dns.HostName
echo "Hop: $ttl`t= $addr`t($name)"
if($reply.Status -eq $success) {break}
}
Edit:
Removed some of the danger by adding a catch statement.
The only danger that is still present is the fact that we only send a single request per hop, which could mean that we don't reach a hop due to a innocent package drop.
Resolving that issue remains a readers exercise.
Hint: (Think of loops within loops)
Bonus: We now attempt to get the dns entry of each hop!
With at least PS 5 you can
Test-Netconnection stackoverflow.com -TraceRoute