Find next available computer name - powershell

I'm trying to find the next available computer name in out domain. Our computers use a naming format
departmentName001
departmentName003
departmentName004
...
departmentName999
I can find the existing computer accounts and add 1 but I can't work out for to get it to start looking at 001, I'm aware of the use of "{0:d3}" -f but I'm not using it correctly. Can anyone help?
function GetComputerList($ComputerName)
{
$objSearcher = New-Object System.DirectoryServices.DirectorySearcher
$objSearcher.SearchRoot = “LDAP://dc=domain,dc=local”
$objSearcher.Filter = ("(&(objectCategory=computer)(name=$ComputerName))")
$colProplist = "name"
$objSearcher.PageSize = 1000
foreach ($i in $colPropList){[void]$objSearcher.PropertiesToLoad.Add($i)}
$colResults = $objSearcher.FindAll()
foreach ($objResult in $colResults)
{$objComputer = $objResult.Properties; $objComputer.name}
}
$HostName = Finance
$unit="{0:d3}" -f $_
$num = GetComputerList("$HostName*") | Foreach {[int]($_.Name)} | Sort-Object | Select-Object -Last 1
$name = $HostName+($unit+($num+1))

Try this, it gets all computer with name starting with 'departmentName', strips all a-z characters, leaving just the numbers, converts the numbers to integers and sorting them to find the largest one:
$searcher = [ADSISearcher]'(&(objectCategory=computer)(name=departmentName*))'
$searcher.PageSize = 1000
$last = $searcher.FindAll() | Foreach-Object { [int]($_.Properties.name -replace '\D').Trim() } | Sort-Object | Select-Object -Last 1
$digitLength = "$last".Length
$NewComputerName = "{0}{1:D$digitLength}" -f 'departmentName',($last+1)
$NewComputerName
EDIT:
# get next available number in a range of numbers. returns 5 for 1,2,3,4,6,7,9
$number = $searcher.FindAll() | Foreach-Object { [int]($_.Properties.name -replace '\D').Trim() } | Sort-Object
for($i=0; $i -lt $number.length; $i++) {if( $number[$i+1]-$number[$i] -gt 1) {$number[$i]+1; break} }

try this:
$searcher = [ADSISearcher]'(&(objectCategory=computer)(name=Finance*))'
$searcher.PageSize = 1000
$last = $searcher.FindAll() | Foreach-Object {
[string]($_.Properties.name -replace '\D') } | Sort-Object
$i = 0
$last | % { if ($i -ne [int]$_ ) { $new = $i.tostring().padleft(3,'0'); break }
else
{ $i++ }}
$newComputerName = "finance" + $new

based on the information in this post I have made a few changes and tweaks to the code for my environment to check more than just AD.. and also fixed it not filling in blanks at the start of a range.. I have blogged it here: AutoGeneratingServer Names
copy of the code here too, and I know it can be refactored lots!
[CmdletBinding()]
param()
# ********************************************************
$startOfName = "xxxYYYZZWEB"
# ********************************************************
# VMWare Details
$ADVIServers = #("vsphere1.blah.local","vsphere2.blah.local","vsphere3.blah.local","vsphere4.blah.local")
$StandAloneHosts = #()
# DNS Details
$DNSServer = "xxxxxx.blah.local"
# SCCM 2012 Details
$SCCM2012SiteServer = "sccm2012.blah.local"
$SCCM2012SiteCode = 'SiteCode'
# SCCM 2007 Details
$SCCM2007SiteServer = "sccm2007.blah.local"
$SCCM2007SiteCode = 'SiteCode2'
# SCOM 2007 Details
$SCOMServer = "scom.blah.local"
# Create Empty Arrays
$VMNumbers = #()
$ADnumbers = #()
$DNSNumbers = #()
$SCCM2012Numbers = #()
$SCCM2007Numbers = #()
$SCOM2007Numbers = #()
# VMWare
Write-Verbose "Processing VMware"
Add-PSSnapin vmware.vimautomation.core -ErrorAction SilentlyContinue
# Set options for certificates and connecting to multiple enviroments
$null = Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$False
$null = Set-PowerCLIConfiguration -DefaultVIServerMode Multiple -Scope User -Confirm:$False
# Connect to each AD Authenticated viServer
foreach ($VIServer in $ADVIServers){$null = Connect-VIServer $VIServer -verbose:$false}
# Connect to standalone host
foreach ($Host in $StandAloneHosts){$null = Connect-VIServer $Host -User 'usernamehere' -Password 'passwordhere' -verbose:$false}
# get next available number in a range of numbers.
$VMNames = Get-VM -Name "$($startOfName)*" -verbose:$false |select Name
$VMNames |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object
$VMNumbers = $VMNames |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object
Write-Verbose "$($VMNumbers.Count) Matching entries found"
# Active Directory
Write-Verbose "Processing Active Directory"
# Issue Query
$searcher = [ADSISearcher]"(&(objectCategory=computer)(name=$($StartOfName)*))"
$searcher.PageSize = 1000
# get next available number in a range of numbers. returns 5 for 1,2,3,4,6,7,9 From AD
$ADNames = $searcher.FindAll() | Foreach-Object {[string]$_.Properties.name} | Sort-Object
$ADNames | Foreach-Object {Write-Verbose $_} | Sort-Object
$ADnumbers = $ADNames | Foreach-Object {[int]($_ -replace '\D').Trim() } | Sort-Object
Write-Verbose "$($ADnumbers.Count) Matching entries found"
# Search DNS
Write-Verbose "Processing DNS"
# Import DNS module
Import-Module dnsShell -Verbose:$false
$DNSNames = get-dnsRecord -server $DNSServer -RecordType A -Zone blah.local | select Name |where {$_.Name -like "$($startOfName)*"}
$DNSNames | Foreach-Object {Write-Verbose $_.Name} | Sort-Object -Unique
$DNSNumbers = $DNSNames | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object -Unique
Write-Verbose "$($DNSNumbers.Count) Matching entries found"
# Search SCCM
Write-Verbose "Processing SCCM 2012"
# Query SCCM2012 Env
$SCCM2012Members = Get-WmiObject -ComputerName $SCCM2012SiteServer -Namespace "ROOT\SMS\site_$SCCM2012SiteCode" -Query "SELECT * FROM SMS_FullCollectionMembership WHERE CollectionID='SMS00001' AND Name LIKE '$($startOfName)%' order by name" | select Name -Unique
$SCCM2012Members |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object
$SCCM2012Numbers = $SCCM2012Members |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object
Write-Verbose "$($SCCM2012Numbers.Count) Matching entries found"
Write-Verbose "Processing SCCM 2007"
# Query SCCM2007 Env
$SCCM2007Names = Get-WMIObject -ComputerName $SCCM2007SiteServer -Namespace "root\sms\site_$SCCM2007SiteCode" -class "SMS_R_System" -filter "Name LIKE `"$startOfName%`"" |select Name | Sort-Object -Property Name -Unique
$SCCM2007Names |select Name | Foreach-Object {Write-Verbose $_.Name} | Sort-Object
$SCCM2007Numbers = $SCCM2007Names |select Name | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object
Write-Verbose "$($SCCM2007Numbers.Count) Matching entries found"
# Search Production SCOM 2007
Write-Verbose "Processing SCOM 2007"
#Initialize SCOM SnapIn
Add-PSSnapin Microsoft.EnterpriseManagement.OperationsManager.Client -ErrorAction SilentlyContinue -verbose:$false
#Connect to Production SCOM 2007 Env.
$null = New-ManagementGroupConnection -ConnectionString $SCOMServer
#Connect to SCOM Provider
Push-Location 'OperationsManagerMonitoring::'
# Get Agents Matching Name
$SCOM2007Names = Get-ManagementServer |Get-Agent |Where {$_.Name -like "$($startOfName)*"}
$SCOM2007Names | Foreach-Object {Write-Verbose $_.Name} | Sort-Object
$SCOM2007Numbers = $SCOM2007Names | Foreach-Object {[int]($_.Name -replace '\D').Trim() } | Sort-Object
Write-Verbose "$($SCOM2007Numbers.Count) Matching entries found"
# Return to previous location
Pop-Location
# Merge arrays adding a zero so we allways start issuing numbers from the beginning (ie 001)
$list = #(0) + $VMNumbers + $ADnumbers + $DNSNumbers + $SCCM2012Numbers + $SCCM2007Numbers + $SCOM2007Numbers
# Remove Duplicates numbers from the array and sort into numerical order
$list = $list | Sort-Object -Unique
Write-Verbose "Used numbers after sorting: $($list)"
# Determine if next server name is a gap in the sequence in the array
for($i=0; $i -lt $list.length; $i++) {
if( $list[$i+1]-$list[$i] -gt 1) {
# The gap between the current server number and the next element in the array is greater than 1
# So we have an available number we can use.
# TODO: - Add support for consecutive numbers IE build 6 servers with consecutive numbers.
$num = "{0:000}" -f ($list[$i]+1)
break
}
}
# If no gap found in the sequence then use the next number from the sequence in the array
if ($num -eq $null) {
$num = "{0:000}" -f (($list[-1]+1))
}
# Construct new name
$NewComputerName = "{0}{1}" -f $startOfName,$num
# Create DNS Record to 'reserve / mark the name as in use'
Write-Verbose "Creating DNS Reservation"
New-DnsRecord -Name $NewComputerName -IPAddress "127.0.0.1" -Zone blah.local -Type A -Server $DNSServer
write-output $NewComputerName

Related

Is there any Powershell script or how can i modify this script to import multiple ips as a csv file if a vm has multiple ip addresses?

Is there any Powershell script or how can i modify this script to import multiple ips as a csv file if a vm has multiple ip addresses ?
This is my existing script
# Create Report Array
$report = #()
# Get all the VMs from the selected subscription
$vms = Get-AzVM
# Get all the Network Interfaces
$nics = Get-AzNetworkInterface | Where-Object { $_.VirtualMachine -NE $null }
foreach ($nic in $nics) {
$ReportDetails = "" | Select-Object ip-address, vm_name, interface_name
$vm = $vms | Where-Object -Property Id -eq $nic.VirtualMachine.id
$ReportDetails.vm_name = $vm.Name
$ReportDetails.ip-address = [String]$nic.IpConfigurations.PrivateIpAddress
$ReportDetails.interface_name = $nic.Name
$report += $ReportDetails
}
$report | Sort-Object VMName | Format-Table ip-address, vm_name, interface_name
$report | Export-Csv -NoTypeInformation -Path $reportFile
}
As a general recommendation, you should try to avoid using the increase assignment operator (+=) to create a collection, besides $null` should be on the left side of the equality comparison.
For the concerned script and expanding the ip_address property this would mean:
# Get all the VMs from the selected subscription
$vms = Get-AzVM
# Get all the Network Interfaces
$nics = Get-AzNetworkInterface | Where-Object { $Null -ne $_.VirtualMachine }
$ReportDetails = foreach ($nic in $nics) {
$vm = $vms | Where-Object -Property Id -eq $nic.VirtualMachine.id
$nic.IpConfigurations.PrivateIpAddress.foreach{
[PSCustomObject]#{
vm_name = $vm.Name
ip_address = $_
interface_name = $nic.Name
}
}
}
csv is not designed to support properties with multivalues (e.g. array). you could use json instead:
$report | convertto-json | set-content -path $reportFile
Or if it has to be a csv you can flattern the structure or join the array to a delimited string, e.g.
$ReportDetails.ip-address = ($nic.IpConfigurations.PrivateIpAddress -join "|")

Optimize slow powershell script

I wrote this script and it works but its painfully slow, can you please point out why ? and provide some ideas on how to optimize its functionality. in can make simple Powershell scrips however I have a very had time looking up methodology on google not knowing what to look for
my script
$i=1;
foreach ($PC in $ComputerName) {
$per = ($i/$ComputerName.Length)*100
try {
# Get-ADComputer $pcs -properties name,enabled | select-object name,enabled
$status = Get-ADComputer -Identity $PC -Properties Enabled | select-object -ExpandProperty Enabled
if(Test-Connection -ComputerName $PC -Quiet -Count 1){
$quserOut = quser.exe /SERVER:$PC 2>&1
if ($quserOut -match "No user exists"){
"$PC>On Line>$status>No users loggedIn"; continue
}else{
$users = $quserOut -replace '\s{2,}', ',' |
ConvertFrom-CSV -Header 'username', 'sessionname', 'id', 'state', 'idleTime', 'logonTime' |
Add-Member -MemberType NoteProperty -Name ComputerName -Value $PC -PassThru
$users = $users[1..$users.count]
for ($i = 0; $i -lt $users.count; $i++){
if ($users[$i].sessionname -match '^\d+$'){
$users[$i].logonTime = $users[$i].idleTime
$users[$i].idleTime = $users[$i].STATE
$users[$i].STATE = $users[$i].ID
$users[$i].ID = $users[$i].SESSIONNAME
$users[$i].SESSIONNAME = $null
}
}
$users = $users | Sort-Object -Property idleTime
# $status = Get-ADComputer -Identity $PC -Properties Enabled | select-object -ExpandProperty Enabled
$Usr = $users | Where-Object { $_.state -eq 'Active' } | select-object -ExpandProperty username
"$PC>On Line>$status>$Usr"
}
} else {
"$PC>Not Online>$status>NoUserDataRetrieve"
}
}
catch {
"$PC>Not in AD>$status>NoUserDataRetrieve"
}
Write-Progress -Activity "Procesando Usuarios:" -Status "Usuario EN Proceso: $i -- $PC" -PercentComplete $per
Start-Sleep -Milliseconds 100
$i++
}
this displays the following data
basically script test if PC exist in Active Directory, it does a ping test , and gets back current logged in user
Machine Name > Ping Test pass? > AD status > current logged in user
PC1>Online>True>BazVic
PC2>NotOnLine>True>No Available Data
PC3>OnLine>True>ReyesDa
PC2>NotOnLine>FALSE>No Available Data

Comparing Arrays within Powershell

I'm looking to do the Following Steps:
Reading in a CSV File
Add The Contents from the CSV File to An Array
Compare the Contents from the CSV with the Contents from another Array
If the Items in the Array aren't members of the CSV Array
Send Email
I First Tried Running the Script with One Host Missing from the Reports2 CSV File, This Missing Host was displayed to the Console and Written to the Reports2 File, but Still when i Re-Run the Code it still displays the last Element (Host that was Missing From Reports2.CSV):
This is the script I'm currently working On:
EDIT: I have now edited the code snippet to reflect the working solution
$user = ''
$pswd = ''
$vCenter_Servers = ""
$now = Get-Date
$start = $now.AddDays(-15)
$esxiHosts = Import-CSV C:\Scripts\Report1.csv #Reading CSV File
$MaitanceMode = #()
$Ticket = #()
foreach($ESXI in $esxiHosts){
$Ticket += $ESXI | Select-Object -ExpandProperty Name
}
foreach($vCenter in $vCenter_Servers) {
$srv = Connect-VIServer -Server $vCenter -User $user -Password $pswd
Get-VMHost -PipelineVariable esx -Server $srv | Where-Object {$_.ConnectionState -eq 'Maintenance'} |
ForEach-Object -Process {
$maintEntered = Get-VIEvent -Entity $esx -Start $start -MaxSamples ([int]::MaxValue) -Server $srv |
Where-Object{$_ -is [VMware.Vim.EnteredMaintenanceModeEvent]}
if($maintEntered){
#Skipping
}
else {
$MaitanceMode += $esx | Select-Object -ExpandProperty Name
}
}
} #Ending ForEach Loop
$NoTicket = $MaitanceMode | Where {$Ticket -Contains $_}
$NoTicket
You should instantiate your array containing the results as an empty array, probably before ForEach-Object -Process {... with $MaitanceMode = #() and when you want to add elements to it, replace this line:
$MaitanceMode = $esx | select name
by
$MaitanceMode += $esx | select name
Edit:
Further replace this line:
$esxiHosts = Import-CSV C:\Scripts\Report2.csv
by this line:
$esxiHosts = Import-CSV C:\Scripts\Report2.csv | Select-Object -ExpandProperty Name
and this line:
$MaitanceMode += $esx | select name
by this line:
$MaitanceMode += $esx | Select-Object -ExpandProperty Name
And don't forget to instantiate $MaitanceMode as an empty array. This is now mandatory. Otherwise it will become a string and not an array.
Despite the accepted answer from #Thomas, it is not correct to use the increase assignment operator (+=) to create a collection in PowerShell. For one thing, it is a very expensive syntax.
see: Why should I avoid using the increase assignment operator (+=) to create a collection.
To build a collection of objects in PowerShell, you should use the PowerShell pipeline by removing the <variable> += of the concerned commands (this will leave the objects on the pipeline) and catch the whole collection by adding <variable> = in front of the iterator (e.g. Foreach-Object). By using this PowerShell syntax, there is no need to initiate the arrays (<variable> = #()).
Taking your script as an example:
$user = ''
$pswd = ''
$vCenter_Servers = ""
$now = Get-Date
$start = $now.AddDays(-15)
$esxiHosts = Import-CSV C:\Scripts\Report1.csv #Reading CSV File
$Ticket = foreach($ESXI in $esxiHosts){
$ESXI | Select-Object -ExpandProperty Name
}
foreach($vCenter in $vCenter_Servers) {
$srv = Connect-VIServer -Server $vCenter -User $user -Password $pswd
Get-VMHost -PipelineVariable esx -Server $srv | Where-Object {$_.ConnectionState -eq 'Maintenance'} |
$MaitanceMode = ForEach-Object -Process {
$maintEntered = Get-VIEvent -Entity $esx -Start $start -MaxSamples ([int]::MaxValue) -Server $srv |
Where-Object{$_ -is [VMware.Vim.EnteredMaintenanceModeEvent]}
if($maintEntered){
#Skipping
}
else {
$esx | Select-Object -ExpandProperty Name
}
}
} #Ending ForEach Loop
$NoTicket = $MaitanceMode | Where {$Ticket -Contains $_}
$NoTicket

If statement invoked twice inside else statement in PowerShell script

I am trying to execute PowerShell script which this code:
function Invoke-InstallationOfANewBuild() {
param (
$PathToTheLocalFolderWhereBuildsAreHeld = "$($env:USERPROFILE)\Desktop\",
$PlaceOnANetworkDriveWhereBuildsAreHeld = "\\r\P\Al\OSystem\D B\20\x64"
)
begin {
Write-Verbose -Message "Searching for a build with the biggest CL number a in build name in local folder." -Verbose
$CheckClNumberOfABuildOnADesktop = Get-ChildItem $PathToTheLocalFolderWhereBuildsAreHeld -Filter *.exe | Where-Object Name -Like '*OSystemInstaller_20*' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1
Write-Verbose -Message "Searching for a build with the biggest CL number in a build name on a network drive." -Verbose
$CheckClNumberOfABuildOnANetworkDrive = Get-ChildItem $PlaceOnANetworkDriveWhereBuildsAreHeld -Filter *.exe | Where-Object Name -NotMatch '.*NoDB\.exe$' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1
Write-Verbose -Message "Comparison of two hash sums. Please, wait." -Verbose
if ($CheckClNumberOfABuildOnADesktop)
{
$GetHashOfFileWhichIsPlacedOnDesktop = Get-MyFileHash $CheckClNumberOfABuildOnADesktop -Algorithm MD5
$GetHashOfFileWhichIsPlacedOnNetworkDrive = Get-MyFileHash $CheckClNumberOfABuildOnANetworkDrive -Algorithm MD5
}
else {
Write-Verbose -Message "There are no O System 20-1 (dev branch) builds in specified local folder. Extracting hash of the newest build in the network folder..." -Verbose
$GetHashOfFileWhichIsPlacedOnNetworkDrive = Get-MyFileHash $CheckClNumberOfABuildOnANetworkDrive -Algorithm MD5
}
if ($GetHashOfFileWhichIsPlacedOnDesktop.MD5 -ne $GetHashOfFileWhichIsPlacedOnNetworkDrive.MD5)
{
Write-Verbose -Message "Hash sum of a file which is placed on the desktop and file in the network drive are different or there is no O System 20-1 build in specified local folder. The newest build will be copied from the network folder to to the local folder." -Verbose
}
else {
Write-Verbose -Message "Hash sum of a file which is placed on the desktop and a file on the network drive are the same. No need to copy anything." -Verbose
}
}
process {
if ($GetHashOfFileWhichIsPlacedOnDesktop.MD5 -eq $GetHashOfFileWhichIsPlacedOnNetworkDrive.MD5){
Write-Verbose -Message "Installation... Please, wait." -Verbose
Get-ChildItem $PathToTheLocalFolderWhereBuildsAreHeld -Filter *.exe | Where-Object Name -Like '*OSystemInstaller_20*' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1 | ForEach-Object { & $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes"}
}
else {
Write-Verbose -Message "The newest build doesn't exist in specified folder. Downloading, please wait." -Verbose
$SelectTheNewestBuildInFolder = Get-ChildItem $PlaceOnANetworkDriveWhereBuildsAreHeld -Filter *.exe | Where-Object Name -NotMatch '.*NoDB\.exe$' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1 | Copy-Item -Destination $PathToTheLocalFolderWhereBuildsAreHeld
}
$HashNumberOfCopiedBuild = Get-MyFileHash $SelectTheNewestBuildInFolder -Algorithm MD5
if ($HashNumberOfCopiedBuild.MD5 -eq $GetHashOfFileWhichIsPlacedOnNetworkDrive.MD5) {
Write-Verbose -Message "Hash sum of the copied file and hash sum of original file are the same. Builds are the same." -Verbose
Write-Verbose -Message "Installation... Please, wait." -Verbose
Get-ChildItem $PathToTheLocalFolderWhereBuildsAreHeld -Filter *.exe | Where-Object Name -Like '*OSystemInstaller*' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1 | ForEach-Object {& $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes"}
}
else {
Write-Verbose -Message "Hash sum of the copied file and hash sum of original file are different. Builds are the same." -Verbose
} # [Block moved]
}
}
Invoke-InstallationOfANewBuild
But the if statement in last else statement works twice (so that installation process is invoked twice). How can I put if statement inside else statement so that it can be invoked only once? Example
1) If value is true in the else than execute installation and stop the script
2) If value is false in the else than move to if in else and execute installation from there.
process {
if ($GetHashOfFileWhichIsPlacedOnDesktop.MD5 -ne $GetHashOfFileWhichIsPlacedOnNetworkDrive.MD5) {
Get-ChildItem $PlaceOnANetworkDriveWhereBuildsAreHeld -Filter *.exe | Where-Object Name -NotMatch '.*NoDB\.exe$' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1 | Copy-Item -Destination $PathToTheLocalFolderWhereBuildsAreHeld
}
$CheckClNumberOfABuildOnADesktop = Get-ChildItem $PathToTheLocalFolderWhereBuildsAreHeld -Filter *.exe | Where-Object Name -NotMatch '.*NoDB\.exe$' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1
$HashNumberOfTheCopiedBuild = Get-MyFileHash $CheckClNumberOfABuildOnADesktop -Algorithm MD5
if ($HashNumberOfTheCopiedBuild.MD5 -eq $GetHashOfFileWhichIsPlacedOnNetworkDrive.MD5) {
Write-Verbose -Message "Installation... Please, wait." -Verbose
Get-ChildItem $PathToTheLocalFolderWhereBuildsAreHeld -Filter *.exe | Where-Object Name -Like '*OrthoSystemInstaller*' | ForEach-Object {
New-Object psobject -Property #{
No = [int]([regex]::Match($_.Name, '(?<=CL)\d+').Value)
Name = $_.FullName
}
} | Sort-Object No -Descending | Select-Object -ExpandProperty Name -First 1 | ForEach-Object {& $_ -s2 -sp"-SilentInstallation=standalone -UpdateMaterials=yestoall -UpgradeDBIfRequired=yes"}
}
else {
Write-Verbose -Message "H" -Verbose
}
}
}
All I did - remove installation from first if and put to the second if.

How can I output all the output of a called command in Powershell script

I have adapted an existing Powershell script to query a list of servers for logged in (or disconnected) user sessions using quser /server:{servername} and output the results into a CSV file. It does output any logged in users but what it doesn't capture is servers that had 0 users or weren't accessible (server offline, rpc not available, etc.). I'm assuming that is because these other conditions are "errors" rather than command output.
So if it hits a server with no users it outputs "No User exists for *" in the console running the script. If it hits a server that it can't reach it outputs "Error 0x000006BA enumerating sessionnames" and on a second line "Error [1722]:The RPC server is unavailable." in the console running the script. So neither of these conditions show in the output.csv file.
I wanted to know if someone could suggest how I could also capture these conditions in the CSV as "$Computer has no users" and "$Computer Unreachable"
Here is the script
$ServerList = Read-Host 'Path to Server List?'
$ComputerName = Get-Content -Path $ServerList
foreach ($Computer in $ComputerName) {
quser /server:$Computer | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = #{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
New-Object -TypeName PSCustomObject -Property $HashProps |
Select-Object -Property ServerName,UserName,State,LogonTime |
ConvertTo-Csv -NoTypeInformation | select -Skip 1 | Out-File -Append .\output.csv
}
}
Anyone curious why I'm using ConvertTo-CSV rather than Export-CSV which would be cleaner, is because the servers I'm running this from are running Powershell 2.0 in which Export-CSV doesn't support -Append. I'm not concerned as the output works for what I need, but if someone has a better suggestion for this feel free to comment.
So we have some updates to the script. If there is any error using quser we capture that as a special entry where the server name will read "Error contacting $computer" and other text that will give context to the error..
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = #{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
IdleTime = ""
LogonTime = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
} Else {
$results | Select-Object -Skip 1 | ForEach-Object {
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
$HashProps = #{
UserName = $CurrentLine[0]
ServerName = $Computer
}
# If session is disconnected different fields will be selected
if ($CurrentLine[2] -eq 'Disc') {
$HashProps.SessionName = $null
$HashProps.Id = $CurrentLine[1]
$HashProps.State = $CurrentLine[2]
$HashProps.IdleTime = $CurrentLine[3]
$HashProps.LogonTime = $CurrentLine[4..6] -join ' '
} else {
$HashProps.SessionName = $CurrentLine[1]
$HashProps.Id = $CurrentLine[2]
$HashProps.State = $CurrentLine[3]
$HashProps.IdleTime = $CurrentLine[4]
$HashProps.LogonTime = $CurrentLine[5..7] -join ' '
}
}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} | Select-Object -Property ServerName,UserName,State,LogonTime |
Export-Csv -NoTypeInformation .\output.csv
Part of the issue is that since this is not a PowerShell cmdlet capturing stderr in order to parse it needs to work differnetly. Playing with $erroractionpreference is an option as well but this is a first draft. We use 2>&1 to capture the error into $results to hide the message from the screen. Then we use an If to see if the last command succeeded.
In the event of an error
I used a switch statement with the error text. So you can tailor the output based on the the text returned in $results
Some minor changes
Most of the rest of your code is the same. I moved the object creation outside the If statement so that errors could be logged and changed to a Export-CSV as PowerShell will work out the details of that for you.
Unless you intend to have multiple passes of this function over time that you want to capture into the same file.
Console Output before export
ServerName UserName State LogonTime
---------- -------- ----- ---------
serverthing01 bjoe Active 4/9/2015 5:42 PM
Error contacting c4093 RPC server is unavailable [1722]
Error contacting c4094 Access is denied [5]
You can see there is output for each server even though the last two had separate reasons for not having proper output. If you ever see "Something else" the there was an error that did not have a specific message attached to the error.
When that happens look under State and the error number is displayed. Then you just need to update the Switch accordingly.
Significant Update
I was not sure what was the issue where is was dropping the extra lines for multiple users but I already have a dynamic parsing code for positionally delimited text so I am bringing that in here.
Function ConvertFrom-PositionalText{
param(
[Parameter(Mandatory=$true)]
[string[]]$data
)
$headerString = $data[0]
$headerElements = $headerString -split "\s+" | Where-Object{$_}
$headerIndexes = $headerElements | ForEach-Object{$headerString.IndexOf($_)}
$data | Select-Object -Skip 1 | ForEach-Object{
$props = #{}
$line = $_
For($indexStep = 0; $indexStep -le $headerIndexes.Count - 1; $indexStep++){
$value = $null # Assume a null value
$valueLength = $headerIndexes[$indexStep + 1] - $headerIndexes[$indexStep]
$valueStart = $headerIndexes[$indexStep]
If(($valueLength -gt 0) -and (($valueStart + $valueLength) -lt $line.Length)){
$value = ($line.Substring($valueStart,$valueLength)).Trim()
} ElseIf ($valueStart -lt $line.Length){
$value = ($line.Substring($valueStart)).Trim()
}
$props.($headerElements[$indexStep]) = $value
}
New-Object -TypeName PSCustomObject -Property $props
}
}
$ServerList = Read-Host 'Path to Server List?'
$ComputerNames = Get-Content -Path $ServerList
$HashProps = #{}
$exportedprops = "ServerName","UserName","State",#{Label="LogonTime";Expression={$_.Logon}}
$ComputerNames | ForEach-Object{
$computer = $_
$results = quser /server:$Computer 2>&1 | Write-Output
If($LASTEXITCODE -ne 0){
$HashProps = #{
UserName = ""
ServerName = "Error contacting $computer"
SessionName = ""
Id = ""
State = $results | Select-String -Pattern '\[.+?\]' | Select -ExpandProperty Matches | Select -ExpandProperty Value
Idle = ""
Time = ""
Logon = ""
}
switch -Wildcard ($results){
'*[1722]*'{$HashProps.UserName = "RPC server is unavailable"}
'*[5]*'{$HashProps.UserName = "Access is denied"}
default{$HashProps.UserName = "Something else"}
}
New-Object -TypeName PSCustomObject -Property $HashProps
} Else {
ConvertFrom-PositionalText -data $results | Add-Member -MemberType NoteProperty -Name "ServerName" -Value $computer -PassThru
}
} | Select-Object -Property $exportedprops |
Export-Csv -NoTypeInformation .\output.csv
Biggest difference here is we use ConvertFrom-PositionalText to parse the details from quser. Needed to zero out $HashProps = #{} which was causing conflicting results across mutliple runs. For good measure got the output of the function and the dummy error data to have the same parameter sets. Used $exportedprops which has a calculated expression so that you could have the headers you wanted.
New Output
ServerName USERNAME STATE LogonTime
---------- -------- ----- ---------
server01 user1 Disc 3/12/2015 9:38 AM
server01 user2 Active 4/9/2015 5:42 PM
Error contacting 12345 Access is denied [5]
Error contacting 12345 RPC server is unavailable [1722]
svrThg1 user3 Active 4/9/2015 5:28 PM
svrThg1 user4 Active 4/9/2015 5:58 PM
svrThg1 user-1 Active 4/9/2015 9:50 PM
svrThg1 bjoe Active 4/9/2015 10:01 PM