powershell result redirect into file - powershell

$z = "slc10nzf" , "slc12vbi"
$cls = gc C:\temp\cls.txt
foreach ($cl in $cls)
{
$vms = Get-Vm -ComputerName (Get-ClusterNode -Cluster $cl)
foreach ($vm in $vms)
{
$name = $vm.Name
if ($z -eq $name)
{
Write-Output "$name, $cl" | Out-File c:\temp\result.txt -Append
}
}
}
we have 4 hyper-v cluster and VM's are running on it
cluster names:
slchypervcl001,slchypervcl002,slchypervcl003,slchycl001
I have created script that to find which VM belongs to which cluster.
the script is working fine but the script redirecting result with duplicate any help appreciate.
the present script output is:
slc10nzf, slchypervcl001
slc12vbi, slchypcl001
slc12vbi,

You're using the incorrect comparison operator.
If you change $z -eq $name to $z -contains $name, your script should work as expected.

Related

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 cmdlet Get-Vm not displaying at the correct time

This is the script in it's most current form, on line 6 of the code I am trying to get it to display the current condition of the virtuals on the host.
when I run the code, it doesn't display until the very end.
It runs properly everywhere else, and it proforms the actions like I want it to. But Get-VM doesn't run until right before the script exits.
Is there some sort of forcing command that makes commands execute that I don't know of? I know it's probably just some stupid mistake I am not seeing.
Help is appreciated, if you need further clarification, just let me know.
#new-alias -Name rh -Value read-host
Get-VM
$AA = rh "Would you like to preform an action?[y/n]"
While($AA -eq "y"){
$A = rh "What would you like to do? `n save?[sa] `n start?[st] `n stop?[x]
`n restart?[re]`n "
# blank-vm -name blank
If($A -eq "sa"){
$s = "Save"
$B = rh "Which VM would you like to $s ?[a for all]"
if($B -eq "a"){
Get-VM | Save-VM
}
else{save-vm -name "$B"}
}
If($A -eq "st"){
$s = "Start"
$B = rh "Which VM would you like to $s ?[a for all]"
if($B -eq "a"){
Get-VM | Start-VM
}
else{Start-vm -name "$B"}
}
If($A -eq "x"){
$s = "Stop"
$B = rh "Which VM would you like to $s ?[a for all]"
if($B -eq "a"){
Get-VM | stop-VM
}
else{Stop-vm -name "$B"}
}
If($A -eq "re"){
$s = "Restart"
$B = rh "Which VM would you like to $s ?[a for all]"
if($B -eq "a"){
Get-VM | restart-VM
}
else{restart-vm -name "$B"}
}
$AA = rh "Would you like to preform another action?[y/n]"
}
This seems to be a problem with the Hyper-V cmdlets and the Read/Write-Host cmdlets, e. g.
Get-VM
Write-Host "Test"
will result in first writing the string Test to the host and then outputting the result of Get-VM. Get-VMHost, Get-VMHardDisk and Get-VMSwitch have the same issue. The "standard" cmdlets like Get-Service and Get-Process are working normally, e. g.
Get-Service
Write-Host "Test"
will first outputting a list of services and then the string Test. Using the Write-Output instead of Write-Host will result in normal behavior too.
To avoid this you can convert your Get-VM output to string, e. g.:
Get-VM | Out-String
Write-Host "Test"
Other cmdlets could have the same issue

How to dump the foreach loop output into a file in PowerShell?

I have wrote the following script to read the CSV file to perform the custom format of output.
Script is below:
$Content = Import-Csv Alert.csv
foreach ($Data in $Content) {
$First = $Data.DisplayName
$Second = $Data.ComputerName
$Third = $Data.Description
$Four = $Data.Name
$Five = $Data.ModifiedBy
$Six = $Data.State
$Seven = $Data.Sev
$Eight = $Data.Id
$Nine = $Data.Time
Write-Host "START;"
Write-Host "my_object="`'$First`'`;
Write-Host "my_host="`'$Second`'`;
Write-Host "my_long_msg="`'$Third`'`;
Write-Host "my_tool_id="`'$Four`'`;
Write-Host "my_owner="`'$Five`'`;
Write-Host "my_parameter="`'$Four`'`;
Write-Host "my_parameter_value="`'$Six`'`;
Write-Host "my_tool_sev="`'$Seven`'`;
Write-Host "my_tool_key="`'$Eight`'`;
Write-Host "msg="`'$Four`'`;
Write-Host "END"
}
The above script executing without any error.
Tried with Out-File and redirection operator in PowerShell to dump the output into a file, but I'm not finding any solution.
Write-Host writes to the console. That output cannot be redirected unless you run the code in another process. Either remove Write-Host entirely or replace it with Write-Output, so that the messages are written to the Success output stream.
Using a foreach loop also requires additional measures, because that loop type doesn't support pipelining. Either run it in a subexpression:
(foreach ($Data in $Content) { ... }) | Out-File ...
or assign its output to a variable:
$output = foreach ($Data in $Content) { ... }
$output | Out-File ...
Another option would be replacing the foreach loop with a ForEach-Object loop, which supports pipelining:
$Content | ForEach-Object {
$First = $_.DisplayName
$Second = $_.ComputerName
...
} | Out-File ...
Don't use Out-File inside the loop, because repeatedly opening the file will perform poorly.

Run N parallel jobs in powershell

I have the following powershell script
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
$list | % {
GetData $_ > $_.txt
ZipTheFile $_.txt $_.txt.zip
...
}
How to run the script block ({ GetDatta $_ > $_.txt ....}) in parallel with limited maximum number of job, e.g. at most 8 files can be generated at one time?
Same idea as user "Start-Automating" posted, but corrected the bug about forgetting to start the jobs that are held back when hitting the else clause in his example:
$servers = #('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n')
foreach ($server in $servers) {
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -ge 4) {
$running | Wait-Job -Any | Out-Null
}
Write-Host "Starting job for $server"
Start-Job {
# do something with $using:server. Just sleeping for this example.
Start-Sleep 5
return "result from $using:server"
} | Out-Null
}
# Wait for all jobs to complete and results ready to be received
Wait-Job * | Out-Null
# Process the results
foreach($job in Get-Job)
{
$result = Receive-Job $job
Write-Host $result
}
Remove-Job -State Completed
The Start-Job cmdlet allows you to run code in the background. To do what you'd ask, something like the code below should work.
foreach ($server in $servers) {
$running = #(Get-Job | Where-Object { $_.State -eq 'Running' })
if ($running.Count -le 8) {
Start-Job {
Add-PSSnapin SQL
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
...
}
} else {
$running | Wait-Job
}
Get-Job | Receive-Job
}
Hope this helps.
It should be really easy with the Split-Pipeline cmdlet of the SplitPipeline module.
The code will look as simple as this:
Import-Module SplitPipeline
$list = invoke-sqlcmd 'exec getOneMillionRows' -Server...
$list | Split-Pipeline -Count 8 {process{
GetData $_ > $_.txt
ZipTheFile $_.txt $_.txt.zip
...
}}
Old thread but I think this could help:
$List = C:\List.txt
$Jobs = 8
Foreach ($PC in Get-Content $List)
{
Do
{
$Job = (Get-Job -State Running | measure).count
} Until ($Job -le $Jobs)
Start-Job -Name $PC -ScriptBlock { "Your command here $Using:PC" }
Get-Job -State Completed | Remove-Job
}
Wait-Job -State Running
Get-Job -State Completed | Remove-Job
Get-Job
The "Do" loop pause the "foreach" when the amount of job "running" exceed the amount of "$jobs" that is allowed to run.
Than wait for the remaining to complete and show failed jobs...
Background jobs is the answer. You can also throttle the jobs in the run queue using [System.Collection.Queue]. There is a blog post from PowerShell team on this topic: https://devblogs.microsoft.com/powershell/scaling-and-queuing-powershell-background-jobs/
Using queuing method is probably the best answer to throttling background jobs.
I use and improove a multithread Function, you can use it like :
$Script = {
param($Computername)
get-process -Computername $Computername
}
#('Srv1','Srv2') | Run-Parallel -ScriptBlock $Script
include this code in your script
function Run-Parallel {
<#
.Synopsis
This is a quick and open-ended script multi-threader searcher
http://www.get-blog.com/?p=189#comment-28834
Improove by Alban LOPEZ 2016
.Description
This script will allow any general, external script to be multithreaded by providing a single
argument to that script and opening it in a seperate thread. It works as a filter in the
pipeline, or as a standalone script. It will read the argument either from the pipeline
or from a filename provided. It will send the results of the child script down the pipeline,
so it is best to use a script that returns some sort of object.
.PARAMETER ScriptBlock
This is where you provide the PowerShell ScriptBlock that you want to multithread.
.PARAMETER ItemObj
The ItemObj represents the arguments that are provided to the child script. This is an open ended
argument and can take a single object from the pipeline, an array, a collection, or a file name. The
multithreading script does it's best to find out which you have provided and handle it as such.
If you would like to provide a file, then the file is read with one object on each line and will
be provided as is to the script you are running as a string. If this is not desired, then use an array.
.PARAMETER InputParam
This allows you to specify the parameter for which your input objects are to be evaluated. As an example,
if you were to provide a computer name to the Get-Process cmdlet as just an argument, it would attempt to
find all processes where the name was the provided computername and fail. You need to specify that the
parameter that you are providing is the "ComputerName".
.PARAMETER AddParam
This allows you to specify additional parameters to the running command. For instance, if you are trying
to find the status of the "BITS" service on all servers in your list, you will need to specify the "Name"
parameter. This command takes a hash pair formatted as follows:
#{"key" = "Value"}
#{"key1" = "Value"; "key2" = 321; "key3" = 1..9}
.PARAMETER AddSwitch
This allows you to add additional switches to the command you are running. For instance, you may want
to include "RequiredServices" to the "Get-Service" cmdlet. This parameter will take a single string, or
an aray of strings as follows:
"RequiredServices"
#("RequiredServices", "DependentServices")
.PARAMETER MaxThreads
This is the maximum number of threads to run at any given time. If ressources are too congested try lowering
this number. The default value is 20.
.PARAMETER SleepTimer_ms
This is the time between cycles of the child process detection cycle. The default value is 200ms. If CPU
utilization is high then you can consider increasing this delay. If the child script takes a long time to
run, then you might increase this value to around 1000 (or 1 second in the detection cycle).
.PARAMETER TimeOutGlobal
this is the TimeOut in second for listen the last thread, after this timeOut All thread are closed, only each other are returned
.PARAMETER TimeOutThread
this is the TimeOut in second for each thread, the thread are aborted at this time
.PARAMETER PSModules
List of PSModule name to include for use in ScriptBlock
.PARAMETER PSSapins
List of PSSapin name to include for use in ScriptBlock
.EXAMPLE
1..20 | Run-Parallel -ScriptBlock {param($i) Start-Sleep $i; "> $i sec <"} -TimeOutGlobal 15 -TimeOutThread 5
.EXAMPLE
Both of these will execute the scriptBlock and provide each of the server names in AllServers.txt
while providing the results to GridView. The results will be the output of the child script.
gc AllServers.txt | Run-Parallel $ScriptBlock_GetTSUsers -MaxThreads $findOut_AD.ActiveDirectory.Servers.count -PSModules 'PSTerminalServices' | out-gridview
#>
Param(
[Parameter(ValueFromPipeline=$true,ValueFromPipelineByPropertyName=$true)]
$ItemObj,
[ScriptBlock]$ScriptBlock = $null,
$InputParam = $Null,
[HashTable] $AddParam = #{},
[Array] $AddSwitch = #(),
$MaxThreads = 20,
$SleepTimer_ms = 100,
$TimeOutGlobal = 300,
$TimeOutThread = 100,
[string[]]$PSSapins = $null,
[string[]]$PSModules = $null,
$Modedebug = $true
)
Begin{
$ISS = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
ForEach ($Snapin in $PSSapins){
[void]$ISS.ImportPSSnapIn($Snapin, [ref]$null)
}
ForEach ($Module in $PSModules){
[void]$ISS.ImportPSModule($Module)
}
$RunspacePool = [runspacefactory]::CreateRunspacePool(1, $MaxThreads, $ISS, $Host)
$RunspacePool.CleanupInterval=1000
$RunspacePool.Open()
$Jobs = #()
}
Process{
#ForEach ($Object in $ItemObj){
if ($ItemObj){
Write-Host $ItemObj -ForegroundColor Yellow
$PowershellThread = [powershell]::Create().AddScript($ScriptBlock)
If ($InputParam -ne $Null){
$PowershellThread.AddParameter($InputParam, $ItemObj.ToString()) | out-null
}Else{
$PowershellThread.AddArgument($ItemObj.ToString()) | out-null
}
ForEach($Key in $AddParam.Keys){
$PowershellThread.AddParameter($Key, $AddParam.$key) | out-null
}
ForEach($Switch in $AddSwitch){
$PowershellThread.AddParameter($Switch) | out-null
}
$PowershellThread.RunspacePool = $RunspacePool
$Handle = $PowershellThread.BeginInvoke()
$Job = [pscustomobject][ordered]#{
Handle = $Handle
Thread = $PowershellThread
object = $ItemObj.ToString()
Started = Get-Date
}
$Jobs += $Job
}
#}
}
End{
$GlobalStartTime = Get-Date
$continue = $true
While (#($Jobs | Where-Object {$_.Handle -ne $Null}).count -gt 0 -and $continue) {
ForEach ($Job in $($Jobs | Where-Object {$_.Handle.IsCompleted -eq $True})){
$out = $Job.Thread.EndInvoke($Job.Handle)
$out # return vers la sortie srandard
#Write-Host $out -ForegroundColor green
$Job.Thread.Dispose() | Out-Null
$Job.Thread = $Null
$Job.Handle = $Null
}
foreach ($InProgress in $($Jobs | Where-Object {$_.Handle})) {
if ($TimeOutGlobal -and (($(Get-Date) - $GlobalStartTime).totalseconds -gt $TimeOutGlobal)){
$Continue = $false
#Write-Host $InProgress -ForegroundColor magenta
}
if (!$Continue -or ($TimeOutThread -and (($(Get-Date) - $InProgress.Started).totalseconds -gt $TimeOutThread))) {
$InProgress.thread.Stop() | Out-Null
$InProgress.thread.Dispose() | Out-Null
$InProgress.Thread = $Null
$InProgress.Handle = $Null
#Write-Host $InProgress -ForegroundColor red
}
}
Start-Sleep -Milliseconds $SleepTimer_ms
}
$RunspacePool.Close() | Out-Null
$RunspacePool.Dispose() | Out-Null
}
}
Old thread, but my contribution to it, is the part where you count the running jobs. Some of the answers above do not work for 0 or 1 running job. A little trick I use is to throw the results in a forced array, and then count it:
[array]$JobCount = Get-job -state Running
$JobCount.Count