Powershell - Exchange JSON output without needing to write to a file - powershell

EDIT: Added Setupconfigfiles.ps1
I'm a bit new to detailed scripting so please bear with me.
I have two Powershell scripts
Setupconfigfiles.ps1 generates JSON output to be fed to an API.
Script2 uses that JSON data to execute API commands.
Script 2 can call setupconfigfiles.ps1 as indicated below and use the output data.
.\SetupConfigFiles.ps1 -type $Type -outfile .\Templist.json
$servers = Get-Content -Raw -Path .\templist.json | ConvertFrom-Json
setupconfigfiles.ps1:
param (
# If this parameter is set, format the output as csv.
# If this parameter is not set, just return the output so that the calling program can use the info
[string]$outfile,
# this parameter can be 'production', 'development' or 'all'
[string]$type
)
enum MachineTypes {
production = 1
development = 2
all = 3
}
$Servers = Get-ADObject -Filter 'ObjectClass -eq "computer"' -SearchBase 'Obfuscated DSN' | Select-Object Name
$output = #()
$count = 0
# Set this to [MachineTypes]::production or [MachineTypes]::development or [MachineTypes]::all
if ($type -eq "all") {
$server_types = [MachineTypes]::all
}
ElseIf ($type -eq "production") {
$server_types = [MachineTypes]::production
}
else {
$server_types = [MachineTypes]::development
}
ForEach ($Server in $Servers)
{
$count = $count + 1
$this_server = #{}
$this_server.hostname = $Server.Name
$this_server.id = $count
$this_server."site code" = $this_server.hostname.substring(1,3)
$this_server."location code" = $this_server.hostname.substring(4,2)
if ($this_server.hostname.substring(7,1) -eq "P") {
$this_server.environment = "Production"
}
ElseIf ($this_server.hostname.substring(7,1) -eq "D") {
$this_server.environment = "Development"
}
Else {
$this_server.environment = "Unknown"
}
if (($server_types -eq [MachineTypes]::production ) -and ($this_server.environment -eq "Production")) {
$output += $this_server
}
ElseIf (($server_types -eq [MachineTypes]::development ) -and ($this_server.environment -eq "Development")) {
$output += $this_server
}
Else {
if ($server_types -eq [MachineTypes]::all ) {
$output += $this_server
}
}
}
if ($outfile -eq "")
{
ConvertTo-Json $output
}
else {
ConvertTo-Json $output | Out-File $outfile
}
How can I do it without needing to write to the Templist.json file?
I've called this many different ways. The one I thought would work is .\SetupConfigFiles.ps1 $servers

Y'all are great. #Zett42 pointed me in a direction and #Mathias rounded it out.
The solution was to change:
"ConvertTo-Json $output" to "Write-Output $output"
Then it's handled in the calling script.
thanks!

Related

Powershell Return only TRUE if All Values are the same

I have the script below to read registry values from a certain key(taking no credit for it). My end goal is to only return TRUE if all the values in the array Match. However I'm not quite getting it as
Example Registry Entry
$array = #()
$regval = Get-Item -Path HKLM:\SOFTWARE\Runner\Event
$regval.GetValueNames() |
ForEach-Object {
$name = $_
$rv.Value
$array += New-Object psobject -Property #{'Value' = $rv.Value }
}
$Matchvalue = 'A'
Foreach ($v in $array){
if ($v -match $Matchvalue){
$true
}
}
Update: I've just tried again and it appears my array is empty. So any tips welcome for me.
How about this:
$regkey = Get-Item HKLM:\SOFTWARE\Runner\Event
$matchPattern = 'A'
$values = $regkey.GetValueNames()
$matchingValues = $values | Where { $regkey.GetValue($_) -match $matchPattern }
# this is going to be true or false
$values.Count -eq $matchingValues.Count
Note that by default, Powershell is case-insensitive. So $matchPattern = 'A' and $matchPattern = 'a' will behave the same.
Here's my attempt to do something like Haskell's "all".
function foldr ($sb, $accum, $list) {
if ($list.count -eq 0) { $accum }
else { & $sb $list[0] (foldr $sb $accum $list[1..$list.length]) }
}
function and ($list) {
foldr { $args[0] -and $args[1] } $true $list
}
function all ($list, $sb) {
and ( $list | foreach $sb )
}
all 1,1,1 { $_ -eq 1 }
True
all 1,2,1 { $_ -eq 1 }
False

Writing an output on a .txt file on Powershell

I found a little script to get all the local groups and members and it's working perfectly but I need to write the output on PowerShell.
Trap {"Error: $_"; Break;}
function EnumLocalGroup($LocalGroup) {
$Group = [ADSI]"WinNT://$strComputer/$LocalGroup,group"
"`r`n" + "Group: $LocalGroup"
$Members = #($Group.psbase.Invoke("Members"))
foreach ($Member In $Members) {
$Name = $Member.GetType().InvokeMember("Name", 'GetProperty', $Null, $Member, $Null)
$Name
}
}
$strComputer = gc env:computername
"Computer: $strComputer"
$computer = [adsi]"WinNT://$strComputer"
$objCount = ($computer.PSBase.Children | Measure-Object).Count
$i = 0
foreach ($adsiObj in $computer.PSBase.Children) {
switch -regex ($adsiObj.PSBase.SchemaClassName) {
"group" {
$group = $adsiObj.Name
EnumLocalGroup $group
}
}
$i++
}
I already tried this:
function EnumLocalGroup($LocalGroup) | Out-File -FilePath "E:\PS\Malik\group.txt"
But the code won't start if I do that. I also tried to use this whole Out-File line at the end of the code after the } but doesn't work either and this is the only solution I find on Internet.
If you want to incorporate logging into a function you need to put it into the function body, e.g.
function EnumLocalGroup($LocalGroup) {
....
$foo = 'something'
$foo # output returned by function
$foo | Add-Content 'log.txt' # output to log file
...
}
or
function EnumLocalGroup($LocalGroup) {
...
$foo = 'something'
$foo | Tee-Object 'log.txt' -Append # output goes to log file and StdOut
...
}
Otherwise you have to do the logging when you call the function:
EnumLocalGroup $group | Add-Content 'C:\log.txt'

Get local group members: version agnostic

I found this thread that offers two basic approaches to getting local group members.
This works for me in all versions of powershell, but depends on using the old NET command line utility.
function Get-LocalGroupMembers() {
param ([string]$groupName = $(throw "Need a name") )
$lines = net localgroup $groupName
$found = $false
for ($i = 0; $i -lt $lines.Length; $i++ ) {
if ( $found ) {
if ( -not $lines[$i].StartsWith("The command completed")) {
$lines[$i]
}
} elseif ( $lines[$i] -match "^----" ) {
$found = $true;
}
}
}
This works for me in PowerShell 2.0, but barfs in PS5.0 with Error while invoking GetType. Could not find member.
It only barfs on some groups, including Administrators, which has me thinking it's some sort of security feature, like requiring elevated privileges to REALLY have admin rights in a script.
Function Get-LocalGroupMembers
{
Param(
[string]
$server = "."
)
Try
{
$computer = [ADSI]"WinNT://$( $Server ),computer"
$computer.psbase.children |
where {
$_.psbase.schemaClassName -eq 'group'
} |
ForEach {
$GroupName = $_.Name.ToString()
$group =[ADSI]$_.psbase.Path
$group.psbase.Invoke("Members") |
foreach {
$memberName = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -replace "WinNT:", ""
$props = #{
"LocalGroup" = $GroupName
"MemberName" = $memberName
}
$obj = New-Object -TypeName psobject -Property $props
Write-Output $obj
} # foreach members
} # foreach group
}
Catch
{
Throw
}
}
I think I read somewhere that PS5.1 has a native CMDlet finally. But I can't depend on a particular version of PS, I need to support everything from PS2.0 in Win7 up. That said, is there a single version agnostic solution that doesn't depend on a command line utility kludge? Or do I need to have code that uses the old hack or the new CMDlet depending on PS version I am running on?
So, I am having good luck with this solution.
$hostname = (Get-WmiObject -computerName:'.' -class:Win32_ComputerSystem).name
$wmiQuery = Get-WmiObject -computerName:'.' -query:"SELECT * FROM Win32_GroupUser WHERE GroupComponent=`"Win32_Group.Domain='$Hostname',Name='$group'`""
if ($wmiQuery -ne $null) {
:searchLoop foreach ($item in $wmiQuery) {
if (((($item.PartComponent -split "\,")[1] -split "=")[1]).trim('"') -eq $user) {
$localGroup = $true
break :searchLoop
}
}
}
I'm not sure yet if I like that overly complex IF vs some variables, but the functionality is there and working across all versions of PS without resorting to command line kludges, which was the goal.
Note that this just returns true if the user is a member of the group, which is all I need. The other code I posted would provide a list of members, which is the basis of doing a check, and I just hadn't modified it to show the real end goal, since the problem manifested without that.
Instead this :
$memberName = $_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null) -replace "WinNT:", ""
You can try this :
$memberName = ([ADSI]$_).InvokeGet("Name")

PowerShell Is there a way to get proper order of properties in Select-Object so that each object has order?

Following code lists all AD users.
$Domain.DomainUsersFullList = Get-ADUser -Server $Domain -ResultPageSize 500000 -Filter * -Properties *, "msDS-UserPasswordExpiryTimeComputed" | Select * -ExcludeProperty *Certificate, PropertyNames, *Properties, PropertyCount, Certificates
$($Domain.DomainUsersFullList[0]).PSObject.Properties.Name[12]
$($Domain.DomainUsersFullList[10]).PSObject.Properties.Name[12]
It seems that order returned by PSObject.Properties.Name can be different. Is there a way to order properties without exclusively telling Select-Object the order you want them in?
Just for the sake of why I need this:
https://github.com/EvotecIT/PSWriteWord - I wrote a function Add-WordTable that takes any $Object and puts this into Word document. No need to parse objects yourself. Just pass it to function and it will be put into Word document.
I am now working on same thing for:
https://github.com/EvotecIT/PSWriteExcel - which has function Add-ExcelWorksheetData that does exactly the same as above with one exception .. since it's Excel you don't have column limit. So with 100 columns I was/am getting wrong order per each row. Which makes no sense.
While in case of WORD document I didn't notice this issue because I never added more then 10 columns, with Excel and 100 columns I was getting wrong order. Below is an example of this:
Here is the method that does the conversion:
function Format-PSTableConvertType2 {
[CmdletBinding()]
param(
$Object,
[switch] $SkipTitles,
[string[]] $ExcludeProperty,
[switch] $NoAliasOrScriptProperties,
[switch] $DisplayPropertySet
)
[int] $Run = 0
$Array = New-ArrayList
$Titles = New-ArrayList
if ($NoAliasOrScriptProperties) {$PropertyType = 'AliasProperty', 'ScriptProperty' } else {$PropertyType = ''}
Write-Verbose "Format-PSTableConvertType2 - Option 2 - NoAliasOrScriptProperties: $NoAliasOrScriptProperties"
foreach ($O in $Object) {
$ArrayValues = New-ArrayList
if ($DisplayPropertySet -and $O.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames) {
$ObjectProperties = $O.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames.Where( { $ExcludeProperty -notcontains $_ } ) #.Name
} else {
$ObjectProperties = $O.PSObject.Properties.Where( { $PropertyType -notcontains $_.MemberType -and $ExcludeProperty -notcontains $_.Name } ).Name
}
#$ObjectProperties = $O.PSObject.Properties
foreach ($Name in $ObjectProperties) {
if ($Run -eq 0 -and -not $SkipTitle) { Add-ToArray -List $Titles -Element $Name }
Add-ToArray -List $ArrayValues -Element $O.$Name
}
if ($Run -eq 0 -and -not $SkipTitle) {Add-ToArray -List ($Array) -Element $Titles }
Add-ToArray -List $Array -Element $ArrayValues
$Run++
}
return , $Array
}
It essentially converts object into Array of Arrays. Which then makes it trivial to just loop thru rows / columns.
Now it's important that while generally I could probably make Get-AdUser display only values I want in proper order I am working on general use modules (PSWriteWord/PSWriteExcel) and I want people to pass any object to it and not have to care about it too much.
Unless anyone has a better option:
$SpecialData = $Domain.DomainUsersFullList | Select-Object $($Domain.DomainUsersFullList[0]).PSObject.Properties.Name
$($Domain.DomainUsersFullList[0]).PSObject.Properties.Name[12]
$($Domain.DomainUsersFullList[10]).PSObject.Properties.Name[12]
$($SpecialData[0] | Select-Object $($Domain.DomainUsersFullList[0]).PSObject.Properties.Name).PSObject.Properties.Name[12]
$($SpecialData[10] | Select-Object $($Domain.DomainUsersFullList[0]).PSObject.Properties.Name).PSObject.Properties.Name[12]
Basically what this does is copy the order of 1st element and applies same order to each and every new line. This ensures that each object will return properties in same order as 1st element.
Final implementation:
function Format-PSTableConvertType2 {
[CmdletBinding()]
param(
$Object,
[switch] $SkipTitles,
[string[]] $ExcludeProperty,
[switch] $NoAliasOrScriptProperties,
[switch] $DisplayPropertySet,
$OverwriteHeaders
)
#[int] $Run = 0
$Array = New-ArrayList
$Titles = New-ArrayList
if ($NoAliasOrScriptProperties) {$PropertyType = 'AliasProperty', 'ScriptProperty' } else {$PropertyType = ''}
Write-Verbose "Format-PSTableConvertType2 - Option 2 - NoAliasOrScriptProperties: $NoAliasOrScriptProperties"
# Get Titles first (to make sure order is correct for all rows)
if ($OverwriteHeaders) {
$Titles = $OverwriteHeaders
} else {
foreach ($O in $Object) {
if ($DisplayPropertySet -and $O.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames) {
$ObjectProperties = $O.psStandardmembers.DefaultDisplayPropertySet.ReferencedPropertyNames.Where( { $ExcludeProperty -notcontains $_ } ) #.Name
} else {
$ObjectProperties = $O.PSObject.Properties.Where( { $PropertyType -notcontains $_.MemberType -and $ExcludeProperty -notcontains $_.Name } ).Name
}
foreach ($Name in $ObjectProperties) {
Add-ToArray -List $Titles -Element $Name
}
break
}
# Add Titles to Array (if not -SkipTitles)
if (-not $SkipTitle) {
Add-ToArray -List $Array -Element $Titles
}
}
# Extract data (based on Title)
foreach ($O in $Object) {
$ArrayValues = New-ArrayList
foreach ($Name in $Titles) {
Add-ToArray -List $ArrayValues -Element $O.$Name
}
Add-ToArray -List $Array -Element $ArrayValues
}
return , $Array
}

Any idea how to resolve this powershell error?

Please forgive me on this one.
I am extremely new to power shell.
I posted a question here yesterday asking for any assisting with reading contents of iis log programmatically.
No response on that yet.
So, after painstaking googling, I found a Power Shell script that appears close to what I am looking for.
When I attempted to execute it, I got the following error:
Get-ChildItem : Cannot find path '\\servername\c$\windows\system32\logfiles\
W3SVC1' because it does not exist.
I am reasonably confident that this is more of a permission issue because the path exists.
Does anyone know how to add username and password to the script below?
Thanks a lot in advance.
#
# Script constants
#
$Server = 'myServer'
$Days = 1
Function Read-IISLog {
[CmdLetBinding()]
Param(
[Parameter(ValueFromPipelineByPropertyName = $True)]
[ValidateScript( { $_ | %{ Test-Path $_ } } )]
[Alias("FullName")]
[String[]]$Path
)
Process {
$Path | ForEach-Object {
$FullPath = (Get-Item $_).FullName
$FileStream = New-Object IO.FileStream($FullPath, "Open", "Read", "ReadWrite")
$StreamReader = New-Object IO.StreamReader($FileStream)
For ($i = 0; $i -lt 4; $i++) {
$Line = $StreamReader.ReadLine()
If ($Line -Match '#Fields: ') {
$Header = ($Line -Replace '#Fields: ').Split(' ', [StringSplitOptions]::RemoveEmptyEntries)
}
}
Do {
$Line = $StreamReader.ReadLine()
If ($Line -NotLike "#*") {
$Line | ConvertFrom-Csv -Delimiter ' ' -Header $Header
}
} Until ($StreamReader.EndOfStream)
}
}
}
$Output = #{}
$Server | ForEach-Object {
Get-ChildItem "\\$_\c$\windows\system32\logfiles\W3SVC1" -Filter *.log |
Where-Object { $_.LastWriteTime -gt (Get-Date).AddDays(-$Days) } |
Read-IISLog |
Where-Object { $_."cs-uri-stem" -Match 'ActiveSync' }
} | Select-Object `
#{n='FirstSync';e={ [DateTime]::ParseExact("$($_.date) $($_.time)", "yyyy-MM-dd HH:mm:ss", $Null) }},
#{n='LastSync';e={ [DateTime]::ParseExact("$($_.date) $($_.time)", "yyyy-MM-dd HH:mm:ss", $Null) }},
#{n='ClientIP';e={ [Net.IPAddress]($_."c-ip") }},
#{n='ClientDevice';e={ $_."cs(User-Agent)" }},
#{n='AuthenticatedUser';e={ $_."cs-username" }},
#{n='Mailbox';e={ $_."cs-uri-stem".Split("/")[2] }},
#{n='DeviceId';e={ $_."cs-uri-stem".Split("/")[-1] }},
#{n='DeviceType';e={ $_."cs-uri-stem".Split("/")[-2] }},
#{n='SyncCount';e={ 1 }} |
ForEach-Object {
If (!$Output.Contains($_.DeviceId)) {
$Output.Add($_.DeviceId, $_)
} Else {
If ($_.FirstSync -lt $Output[$_.DeviceId].FirstSync) { $Output[$_.DeviceId].FirstSync = $_.FirstSync }
If ($_.LastSync -gt $Output[$_.DeviceId].LastSync) { $Output[$_.DeviceId].LastSync = $_.LastSync }
$Output[$_.DeviceId].SyncCount += 1
}
}
$Output.Values
This is a desperate attempt at resolving this as users are expecting a demo tomorrow.
Thank you good people for your assistance.
I found a better solution, one that I can manage better - LogParser:
https://mlichtenberg.wordpress.com/2011/02/03/log-parser-rocks-more-than-50-examples/