netsh result to a PowerShell object - regex

I am trying to work with NETSH from PowerShell. I want see a result from this command such as an object, but netsh returns a string:
netsh wlan show hostednetwork | Get-Member
TypeName: System.String
...
My script must work on system with rather localization, and I can't use -match for parsing a string to an object directly.
How I can solve my trouble?

$netshResult = Invoke-Command -Computername localhost {netsh int tcp show global}
$result = #{}
$netshObject = New-Object psobject -Property #{
ReceiveSideScalingState = $Null
ChimneyOffloadState = $Null
NetDMAState = $Null
}
$netshResult = $netshResult | Select-String : #break into chunks if colon only
$i = 0
while($i -lt $netshResult.Length){
$line = $netshResult[$i]
$line = $line -split(":")
$line[0] = $line[0].trim()
$line[1] = $line[1].trim()
$result.$($line[0]) = $($line[1])
$i++
}
$netshObject.ReceiveSideScalingState = $result.'Receive-Side Scaling State'
$netshObject.ChimneyOffloadState = $result.'Chimney Offload State'
$netshObject.NetDMAState = $result.'NetDMA State'

You got a few alternatives, none of which are nice.
1) Read the netsh output into a string[] and use a custom record parser to create your own object. That is, look at the output on different locales and find out if, say, Hosted newtork settings is always the first header followed by bunch of - characters. If that's the case, assume that next element in array is Mode and so on. This is very error prone, but usually MS command line tools only translate messages, not their order.
2) Look for .Net API for the same information. There is System.Net.NetworkInformation which contains a bunch of connection things. It's a start, though I am not sure if it has info you need.
3) Failing the previous options, use P/Invoke to call native Win32 API. It's a lot of work, so look for pre-existing wrapper libraries before rolling your own.

I recently wrote a cmdlet to parse arbitrary, multi-line text using regular expressions, called ConvertFrom-Text. (Not a great name, if you ask me, but it conforms to the PowerShell naming rules; suggestions are welcome!) So assuming you have that cmdlet, here is one possible solution to your question. (Caveat emptor! The regular expression given was derived from a very small sample of netsh output, so may need some tuning.)
$regex = [regex] '(?ms)(?:^\s*$\s*)?^(?<section>.*?)\s*-+\s*(?<data>.*?)\s*^\s*$'
$result = netsh wlan show hostednetwork | Out-String |
ConvertFrom-Text -pattern $regex -multiline
$result | % {
$dataObj = [PsCustomObject]#{}
$_.Data -split "`r`n" | % {
$element = $_ -split '\s*:\s*'
Add-Member -InputObject $dataObj -MemberType NoteProperty -Name $element[0].Trim() -Value $element[1].Trim()
}
$_.Data = $dataObj # Replace data text with data object
}
$result
On my test system, netsh wlan show hostednetwork returns this:
Hosted network settings
-----------------------
Mode : Allowed
Settings : <Not configured>
Hosted network status
---------------------
Status : Not available
And the output of the $result variable in the code above yields this:
section data
------- ----
Hosted network settings #{Mode=Allowed; Settings=<Not configured>}
Hosted network status #{Status=Not available}
So $result is an array of objects with section and data properties, and the latter is an object with properties defined by the output of the netsh command.
Of course, the above does not get you very far without the ConvertFrom-Text cmdlet. So here is the implementation. (I have copious documentation and examples for it, which will be publicly available once I eventually add it to my open-source PowerShell library.)
filter ConvertFrom-Text
{
[CmdletBinding()]
Param (
[Parameter(Mandatory=$true,Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string[]]$InputObject,
[Parameter(Mandatory=$true,Position=1)]
[regex]$Pattern,
[switch]$RequireAll,
[switch]$Multiline
)
if ($Multiline) {
$dataString = $InputObject -join "`n"
IterateByMatch $dataString $Pattern
}
else {
IterateByLine $InputObject $Pattern
}
}
function IterateByLine([string[]]$data, [regex]$regex)
{
$data | ForEach-Object {
if ($PSItem -match $regex)
{
New-Object PSObject -Property (GetRegexNamedGroups $matches)
}
elseif ($RequireAll) {
throw "invalid line: $_"
}
}
}
function IterateByMatch([string[]]$data, [regex]$regex)
{
$regex.matches($data) | Foreach-Object {
$match = $_
$obj = new-object object
$regex.GetGroupNames() |
Where-Object {$_ -notmatch '^\d+$'} |
Foreach-Object {
Add-Member -InputObject $obj NoteProperty `
$_ $match.groups[$regex.GroupNumberFromName($_)].value
}
$obj
}
}
function Get-RegexNamedGroups($hash)
{
$newHash = #{};
$hash.keys | ? { $_ -notmatch '^\d+$' } | % { $newHash[$_] = $hash[$_] }
$newHash
}

Related

PowerShell filer out invalid AD users using -filte {SamAccountName -eq $_} but with regex

I am trying to filter AD for user names based on computer names which contain the user name, like XXXXXX01BLOGGSJ (BLOGGSJenter code here is the user name in this example)
In order to extract the user name, I use this method:
"XXXXXX01BLOGGSJ" | %{($_ -split '\d+')[-1]}
The output is BLOGGSJ
However, I need to filter many computer names like this, a small percentage of which have invalid usernames in the machine name like "XXXXXX01RUBBISH"
In order to stop the inevitable errors from appearing I am trying to use the -filter {SamAccountName $_} method which works like this:
"BLOGGSJ", "RUBBISH" | % {Get-ADUser -Server domain.com -Filter{SamAccountName -eq $_ }} | select Name
But not when I attempt to do this, which is what I want to do:
“XXXXXX01BLOGGSJ”, “XXXXXX01BLOGGSJ” | % {Get-ADUser -Server domain.com -Filter{SamAccountName -eq "'($_ -split '\d+')[-1]'"}} | select Name
……or various permutations of that. So I am struggling with the syntax I think.
I know I can do this instead:
"XXXXXX01BLOGGSJ","XXXXXX01RUBBISH" | %{($_ -split '\d+')[-1]} | %{Get-ADUser -Server domain.com -Filter {SamAccountName -eq $_ }} | Select Name
but there is something else happening further down the pipe that requires me to do it in the way shown above.
Any help please.
Especially because you say something else is happening further down, I would suggest not trying to do all in a one-line code.
This should get you on your way:
"XXXXXX01BLOGGSJ","XXXXXX01RUBBISH" | ForEach-Object {
$name = ($_ -split '\d+')[-1]
$user = Get-ADUser -Server domain.com -Filter "SamAccountName -eq '$name'" -ErrorAction SilentlyContinue
if ($user) {
# a user with that SamAccountName was found
[PsCustomObject]#{
ComputerName = $_
SamAccountName = $user.SamAccountName
UserName = $user.Name
}
}
else {
# user not found
[PsCustomObject]#{
ComputerName = $_
SamAccountName = $name
UserName = "User Not found in AD"
}
}
}
Output:
ComputerName SamAccountName UserName
------------ -------------- --------
XXXXXX01BLOGGSJ bloggsj Joe Bloggs
XXXXXX01RUBBISH RUBBISH User Not found in AD

parsing solution file using powershell

I have a solution file that includes some projects inside and I'd like to delete some of them using PowerShell.
The aim is to delete a block of text that contains a string (let's say "abcxyz") starting with "Project" and ends with "EndProject" in the next line (or more than that).
For example:
Project("{1111-2222-FFFF-3333}") = "AutoRun", "..\generate\Infra\generate\generate.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{5555-2222-FFFF-3333}") = "SetupSec", "..\generate\Setup.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{4444-2222-FFFF-3333}") = "Common.Fyyy", "..\generate\Infra\Common\Common.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{9999-2222-FFFF-3333}") = "Command.Console", "..\generate\Path\Console.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
EndProject
Project("{7777-2222-FFFF-3333}") = "Infra.GUI", "..\..\generate\GUI.csproj", "{999999-UUUUUU-GGGGGG-ABCDEFGH}"
ProjectSection(ProjectDependencies) = postProject
{AAAA-2222-FFFF-3333} = {999999-UUUUUU-GGGGGG-ABCDEFGH}
EndProjectSection
EndProject
In the example above, I'd like to remove projects contains the string "Infra" in any case.
Is there a simple way of doing it using PowerShell Regex?
(deletion should be done to entire solution file from Project to EndProject)
Thanks,
Shai.
I came here looking for a similar solution. What I did was create this powershell function. It may just be a good jumping off point where you can tailor to your more specific requirements, but it works for me. Enjoy...
function Remove-SourceControl() {
param (
[string]$sourceFilePathname
)
$tempFile = New-TemporaryFile
$filterOn = $false
$fileVersionCorrect = $false
$backupFile = "$sourceFilePathname (backup)"
Copy-Item $sourceFilePathname $backupFile
$rowIndex = 1
Get-Content -Path:$sourceFilePathname | ForEach-Object {
if ($_ -like "*Microsoft Visual Studio Solution File, Format Version 12.00*" ) {
$fileVersionCorrect = $true
}
if ($fileVersionCorrect -eq $true) {
if ($_ -like "*TeamFoundationVersionControl*") { $filterOn = $true }
if (!$filterOn) {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
if ($filterOn -and $_ -like "*EndGlobalSection*") { $filterOn = $false }
} else {
Write-Output $_ | Out-File -FilePath:$tempFile -Append
}
$rowIndex++
}
if ($fileVersionCorrect -eq $true) {
Copy-Item $tempFile $sourceFilePathname -Force
}
Remove-Item $tempFile
}
$path = "c:\some-folder-pathname"
Remove-SourceControl "$path\MySoluton.sln"
Assuming your actual file is consistent with the sample data, you can do this without needing to mess with regex at all:
Get-Content $ProjectFile -Delimiter 'EndProject' |
Where-Object {$_ -notlike '*Infra*'} |
Add-Content $NewProjectFile
That will break up the file into separate projects, and then filter out any of them that contain the string "Infra", and write the rest to a new file.
Your question is "is there a simple way in Powershell" and the answer is: No.
Solution files are a nasty old format with various widely-separated parts that depend on each other. It is possible to edit them by hand but it is hard to get right and easy to get wrong.
If you simply want to delete some projects, open the file in Visual Studio, delete the projects, and save it.
If this question is to solve part of a larger problem, you should ask a new question, how to solve that larger problem.

Creating a ticket in VMWare using PowerCLI when free storage in datastore is too less

I am able to get the free space as ouptut by using the following code.
$body +=echo "------------Free space on Datastore.--------------"`r`n""`r`n""
$body +=get-datastore -name *datastore1* | sort Name | ForEach-Object {
$output=New-Object -TypeName PSObject -Property #{
Freespace = "$([math]::Round($_.FreeSpaceGB, 2)) GB"
Datastore_Name = $_.Name
}
}
Write-Output $output
Is it possible to raise a ticket if the free space is less than 2 GB? If so, how should I change my code?
EDIT :
if (get-datastore | where {$_.FreeSpaceGB -lt 2}){"dosomething"}
or
foreach ($ds in (get-datastore | where {$_.FreeSpaceGB -lt 2})){"dosomething"}

vCenter VM permission query using Powershell / PowerCLI

I've been trying to use Powershell with imported PowerCLI commands for VMware administration and I've hit a snag. What I'm trying to do is query all VM's in a location (doesn't matter where), and for every VM I want the group with "Virtual Machine User with Snapshot" permission, and then use that group name to run a Get-ADGroupMembers query for everyone in that group. I also have to remove the domain prefix from the AD query, which would otherwise cause an error.
After some more playing around with outputting hash table information into the csv, as opposed to 'SystemObject[]', I finally got the script so it doesn't return errors EXCEPT on VM's where there is more than one group. It throws an error but the script continues, and just outputs the members of the first group.
How do I get it to do a recursive AD query for every group that is pulled into the owner groups hashtable? The output would be the same as for all the other VM's, just with a line for each group and members.
$AllVMs = #()
$vms = get-vm * -Location datacenter
foreach ($vm in $vms)
{
$owners = Get-VIPermission $vm.name | where-object {$_.role -eq "virtual machine user with snapshot"}
foreach ($owner in $owners)
{
$members = Get-ADGroupMember ($owners.principal -replace '^prefix\\')
$temp = New-Object psobject |
Add-Member Noteproperty "Name" -value $vm.name -PassThru |
Add-Member Noteproperty "Owner" -value (#($owners.principal) -join ',') -PassThru |
Add-Member Noteproperty "Members" -value (#($members.SamAccountName) -join ',') -passthru
$AllVMs+=$temp
}
$AllVMs | Export-Csv -Path c:\users\me\desktop\AllVMs.csv
I was playing around with it some more today and figured it out! I'm running the script right now against a datacenter with 350+ machines so technically I don't know 100% that it works, but it worked against 3 machines :-) I also added a line to list every machine that's owned by more than one group - handy for troubleshooting. Here's the script:
$AllVMs = #()
$vms = get-vm -Location DATACENTER
foreach ($vm in $vms)
{
$owners = #(Get-VIPermission $vm.name | where-object {$_.role -eq "virtual machine user with snapshot"})
if ($owners.count -gt 1) {write-host "** Note ** '$vm' has"$owners.count "owner groups"}
foreach ($owner in $owners)
{
$members = Get-ADGroupMember ($owner.principal -replace '^prefix\\')
$temp = New-Object psobject |
Add-Member Noteproperty "Name" -value $vm.name -PassThru |
Add-Member Noteproperty "Owner" -value (#($owner.principal) -join ',') -PassThru |
Add-Member Noteproperty "Members" -value (#($members.SamAccountName) -join ',') -PassThru
$AllVMs+=$temp
}
}
$AllVMs
Change $owners.principal to $owner.principal after $members = Get-ADGroupMember? In case you have nested AD groups, Get-ADGroupMember has a -Recursive parameter.

Access next webpage after clicking

Requirement : After clicking on webpage named in $ie.Navigate below. I Need to access HTML / OuterHTML source of Web-page which opens next.
Ex: When I open https://www.healthkartplus.com/search/all?name=Sporanox (by setting $control = Sporanox), below code simply clicks on first matching link. After link is clicked, I need to access HTML of resulting page.
Update : referred another SO question and learned that we can search appropriate window. Code seems to be working for some scenarios but not for all. For $ie2 I get problem accessing Document property.
function getStringMatch
{
# Loop through all 2 digit combinations in the $path directory
foreach ($control In $controls)
{
$ie = New-Object -COMObject InternetExplorer.Application
$ie.visible = $true
$site = $ie.Navigate("https://www.healthkartplus.com/search/all?name=$control")
$ie.ReadyState
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$link = $null
$link = $ie.Document.get_links() | where-object {$_.innerText -eq "$control"}
$link.click()
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$ie2 = (New-Object -COM 'Shell.Application').Windows() | ? {
$_.Name -eq 'Windows Internet Explorer' -and $_.LocationName -match "^$control"
}
# NEED outerHTML of new page. CURRENTLY it is working for some.
$ie.Document.body.outerHTML > d:\med$control.txt
}
}
$controls = "Sporanox"
getStringMatch
I think the issue is when you look for the links in the first page.
The link innerText is not equal to $control, it contains $control i.e. innerText is "Sporanox (100mg)".
The following might help:
$link = $ie.Document.get_links() | where-object {if ($_.innerText){$_.innerText.contains($control)}}
EDIT
Here is the complete code I'm using:
function getStringMatch
{
# Loop through all 2 digit combinations in the $path directory
foreach ($control In $controls)
{
$ie = New-Object -COMObject InternetExplorer.Application
$ie.visible = $true
$site = $ie.Navigate("https://www.healthkartplus.com/search/all?name=$control")
$ie.ReadyState
while ($ie.Busy -and $ie.ReadyState -ne 4){ sleep -Milliseconds 100 }
$link = $null
$link = $ie.Document.get_links() | where-object {if ($_.innerText){$_.innerText.contains($control)}}
$link.click()
while ($ie.Busy)
{
sleep -Milliseconds 100
}
# NEED outerHTML of new page. CURRENTLY it is working for some.
$ie.Document.body.outerHTML > d:\med$control.txt
}
}
$controls = "Sporanox"
getStringMatch