If statement invoked twice inside else statement in PowerShell script - powershell

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.

Related

Filter file "yyyy-MM-dd_HH-mm-ss_Computername_Username_File.json" by Computername/Username

I have a folder containing text-files with a standardized naming-scheme like:
2021-03-16_21-25-55_Client1_Edward.Hall_ServerResponse.json
2021-03-16_21-25-33_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-17-38_Client3_Millie.Walsh_ServerResponse.json
2021-03-16_21-17-30_Client4_Lilly.Morton_ServerResponse.json
2021-03-16_21-15-45_Client5_Tia.Curtis_ServerResponse.json
2021-03-16_21-15-23_Client1_Edward.Hall_ServerResponse.json
2021-03-16_21-15-10_Client1_Lilly.Morton_ServerResponse.json
2021-03-16_21-15-03_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-12-14_Client2_Eloise.Glover_ServerResponse.json
2021-03-16_21-11-25_Client3_Administrator_ServerResponse.json
I want to filter the files and retrieve the latest file (LastWriteTime) of a specific Computername-/Username-combination. Therefore I want to use a code like this:
# $env:COMPUTERNAME = "Client1"
# $env:USERNAME = "Edward.Hall"
$MyFolder = "C:\MyFolder"
Get-ChildItem -Path $MyFolder -File -ErrorAction SilentlyContinue | Where-Object {
$_.Extension -eq ".json" -and $_.COMPUTERNAME -eq $env:COMPUTERNAME -and $_.USERNAME -eq $env:USERNAME
} | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1
Of course the part -and $_.COMPUTERNAME -eq $env:COMPUTERNAME -and $_.USERNAME -eq $env:USERNAME is NOT working and should only show up the direction to what I imagine.
In the example above the result should be the file "2021-03-16_21-25-55_Client1_Edward.Hall_ServerResponse.json".
I was thinking of using -match, but it should be a exact match -eq.
Could you please help me to find a solution for this?
Thank you very much!
As long as you can count on the name format always conforming to that standard you can just split up the name strings for your required sections:
# $env:COMPUTERNAME = "Client1"
# $env:USERNAME = "Edward.Hall"
$MyFolder = "C:\MyFolder"
Get-ChildItem -Path $MyFolder -File -ErrorAction SilentlyContinue | Where-Object {
($_.Extension -eq ".json") -and ($_.Name.Split('_')[2] -eq $env:COMPUTERNAME) -and ($_.Name.Split('_')[3] -match $env:USERNAME)
} | Sort-Object -Descending -Property LastWriteTime | Select-Object -First 1

Getting effective user Permissions for many directories

Usually $Plist would be an array but for example we take just one directory.
My problem is I can't use the $ids var. Somehow I cant read out the data and can't bypass it to:
Get-ADGroup -Identity $id -Properties member | Select-Object -ExpandProperty member
I need the usernames per directory with their group names.
Like : Path GroupName UserList
Can someone help? Maybe tweak my code or make something similar :)
$plist = "\\Server\Share"
$FList = foreach($dir in $Plist)
{
Resolve-Path -Path $dir
Get-Acl -Path $dir -Filter Access | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -like "Domain\*"} | Select-Object IdentityReference
Get-Item $dir | select FullName
}
$Flist | ft FullName, IdentityReference
$identity = $Flist.IdentityReference | out-string
$ids = foreach($ident in $identity)
{
$ident = $ident.Replace("Domain\","")
$ident
}
foreach($id in $ids)
{
$id
Get-ADGroup -Identity $id -Properties member | Select-Object -ExpandProperty member
}
Do not use ft (Format-Table) or Out-String on values that you may ned later in your script.
$ids = foreach($ident in $Flist.IdentityReference){
"$ident".Replace('Domain\','')
}
You could also strip the domain prefix from all the user names in one go with the -replace operator:
foreach($id in $flist.IdentityReference.Value -replace 'Domain\\')
{
Get-ADGroup $id -Properties member | Select-Object -ExpandProperty member
}
The Final Script is this, for people who might need something similar. So you can read out the effective permissions and show the group member of permission granted groups.
$ErrorActionPreference = "SilentlyContinue"
$Path = "\\Server\Share\Logs\"
$Log = $Path + "Effective_Permissions" + ".log"
$PPath = Read-Host "Enter Path to scan"
$plist = Get-Childitem -Path $PPath -Recurse | ?{ $_.PSIsContainer } | Select-Object FullName
foreach($Dir in $PList)
{
$Dir = $Dir -replace "#{FullName=", "" -replace "}"
Resolve-Path -Path $Dir
Write-Output "`n" | Out-File $log -append
Write-Output "#######################################################################" | Out-File $Log -append
Get-Item $Dir | select FullName | Out-File $Log -append
$AclList = Get-Acl -Path $Dir -Filter Access | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -like "Domain\*"} | Select-Object IdentityReference
Get-Acl -Path $dir -Filter Access | Select-Object -ExpandProperty Access | Where-Object {$_.IdentityReference -like "Domain\*"} | Out-File $Log -append
foreach($Id in $AclList.IdentityReference.Value -replace 'Domain\\')
{
$ADGroup = Get-ADGroup $Id -Properties member | Select-Object -ExpandProperty member
Write-Output "`n" | Out-File $Log -append
Write-Output "Member of $Id `n
---------------------------------" | Out-File $Log -append
foreach ($Object in $ADGroup)
{
$Group = Get-ADUser -filter * -SearchBase "$Object"
if($Group -ne $null)
{
$GrName = $Group.Name
Write-Output "$GrName" | Out-File $Log -append
}
}
}
Clear-Variable Object, Group, ADGroup, ACLList, GRName, Id
}

Powershell How to Modify Script to Hardcode " | export-csv c:\temp\filename.csv -notypeinformation"

I have this awesome script I use to generate a list of folders with their assigned security groups and each user in each group.
When I run it, I type .\getfolderacls.ps1 -verbose | export-csv c:\temp\filename.csv -notypeinformation.
That works perfectly, but I'd like to hardcode the | export-csv... part so that I can just run it without the arguments (or are they parameters?).
I tried simply appending | export-csv c:\temp\test.csv -notypeinformation to the bottom of the script, but that throws the error An empty pipe element is not allowed.
Script:
[CmdletBinding()]
Param (
[ValidateScript({Test-Path $_ -PathType Container})]
[Parameter(Mandatory=$false)]
[string]$Path
)
Write-Verbose "$(Get-Date): Script begins!"
Write-Verbose "Getting domain name..."
$Domain = (Get-ADDomain).NetBIOSName
Write-Verbose "Getting ACLs for folder $Path"
Write-Verbose "...and all sub-folders"
Write-Verbose "Gathering all folder names, this could take a long time on bigger folder trees..."
$Folders = Get-ChildItem -Path I:\foldername -Directory -Recurse -Depth 2
Write-Verbose "Gathering ACL's for $($Folders.Count) folders..."
ForEach ($Folder in $Folders)
{ Write-Verbose "Working on $($Folder.FullName)..."
$ACLs = Get-Acl $Folder.FullName | ForEach-Object { $_.Access | where{$_.IdentityReference -ne "BUILTIN\Administrators" -and $_.IdentityReference -ne "BUILTIN\Users" }}
ForEach ($ACL in $ACLs)
{ If ($ACL.IdentityReference -match "\\")
{ If ($ACL.IdentityReference.Value.Split("\")[0].ToUpper() -eq $Domain.ToUpper())
{ $Name = $ACL.IdentityReference.Value.Split("\")[1]
If ((Get-ADObject -Filter 'SamAccountName -eq $Name').ObjectClass -eq "group")
{ ForEach ($User in (Get-ADGroupMember $Name -Recursive | Select -ExpandProperty Name))
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = $Name
User = $User
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
Else
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = ""
User = Get-ADUser $Name | Select -ExpandProperty Name
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
Else
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = ""
User = $ACL.IdentityReference.Value
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
}
}
Write-Verbose "$(Get-Date): Script completed!"
Your script's output is being produced inside a foreach loop - ForEach ($Folder in $Folders) ... (as opposed to via the ForEach-Object cmdlet, which, unfortunately, is also aliased to foreach).
In order to send a foreach loop's output to the pipeline, you can wrap it in a script block ({ ... }) and invoke it with the dot-sourcing operator (.).
Alternatively, use the call operator (&), in which case the loop runs in a child scope.
Here are simplified examples:
# FAILS, because you can't use a foreach *loop* directly in a pipeline.
PS> foreach ($i in 1..2) { "[$i]" } | Write-Output
# ...
An empty pipe element is not allowed.
# ...
# OK - wrap the loop in a script block and invoke it with .
PS> . { foreach ($i in 1..2) { "[$i]" } } | Write-Output
[1]
[2]
Note: I'm using Write-Output as an example of a cmdlet you can pipe to, solely for the purpose of this demonstration. What's required in your case is to wrap your foreach loop in . { ... } and to follow it with | Export-Csv ... instead of Write-Output.
Using . { ... } or & { ... } sends the output generated inside the loop to the pipeline as it is being produced, one by one, aka in streaming fashion - as (typically) happens with output produced by a cmdlet.
An alternative is to use $(...), the subexpression operator (or #(...), the array-subexpression operator, which works the same in this scenario), in which case the loop output is collected in memory as a whole, up front, before it is sent through the pipeline - this is typically faster, but requires more memory:
# OK - call via $(...), with output collected up front.
PS> $(foreach ($i in 1..2) { "[$i]" }) | Write-Output
[1]
[2]
To spell the . { ... } solution out in the context of your code - the added lines are marked with # !!! comments (also note the potential to improve your code based on Lee_Dailey's comment on the question):
[CmdletBinding()]
Param (
[ValidateScript({Test-Path $_ -PathType Container})]
[Parameter(Mandatory=$false)]
[string]$Path
)
Write-Verbose "$(Get-Date): Script begins!"
Write-Verbose "Getting domain name..."
$Domain = (Get-ADDomain).NetBIOSName
Write-Verbose "Getting ACLs for folder $Path"
Write-Verbose "...and all sub-folders"
Write-Verbose "Gathering all folder names, this could take a long time on bigger folder trees..."
$Folders = Get-ChildItem -Path I:\foldername -Directory -Recurse -Depth 2
Write-Verbose "Gathering ACL's for $($Folders.Count) folders..."
. { # !!!
ForEach ($Folder in $Folders)
{ Write-Verbose "Working on $($Folder.FullName)..."
$ACLs = Get-Acl $Folder.FullName | ForEach-Object { $_.Access | where{$_.IdentityReference -ne "BUILTIN\Administrators" -and $_.IdentityReference -ne "BUILTIN\Users" }}
ForEach ($ACL in $ACLs)
{ If ($ACL.IdentityReference -match "\\")
{ If ($ACL.IdentityReference.Value.Split("\")[0].ToUpper() -eq $Domain.ToUpper())
{ $Name = $ACL.IdentityReference.Value.Split("\")[1]
If ((Get-ADObject -Filter 'SamAccountName -eq $Name').ObjectClass -eq "group")
{ ForEach ($User in (Get-ADGroupMember $Name -Recursive | Select -ExpandProperty Name))
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = $Name
User = $User
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
Else
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = ""
User = Get-ADUser $Name | Select -ExpandProperty Name
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
Else
{ $Result = New-Object PSObject -Property #{
Path = $Folder.Fullname
Group = ""
User = $ACL.IdentityReference.Value
FileSystemRights = $ACL.FileSystemRights
}
$Result | Select Path,Group,User,FileSystemRights
}
}
}
}
} | Export-Csv c:\temp\test.csv -notypeinformation # !!!
Write-Verbose "$(Get-Date): Script completed!"

Powershell Script assistance required

I'm piping a file's contents to Select-Object and creating two properties for each name, ComputerName and FileExists, where the latter value is the result of Test-Path
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | `
Select-Object #{Name='ComputerName';Expression={$_}},#{Name='FolderExist';Expression={ Test-Path "\\$_\c$\Data\Repository"}},
#{Name='Size';Expression={$_.Sum}}
I want to return the size of this folder if it exists on each server. How would you do it ?
Tried adding
#{Name='Size';Expression={$_.Sum}}
to my select-object but that does not return any value
I think this is what you are looking for
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | Select-Object #{Name='ComputerName';Expression={$_}},#{Name='FolderExist';Expression={ Test-Path "\\$_\c$\Data\Repository"}}, #{Name='Size';Expression={ "{0:n2}" -f ((gci -path "\\$_\c$\Data\Repository" -recurse | measure-object -property length -sum).sum /1mb) + " mb" }}
Since at least one calculated property depends on the other, you would have to do at least 2 Select-Object statements, but I would probably use ForEach-Object and New-Object instead:
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | ForEach-Object {
New-Object psobject -Property #{
'ComputerName' = $_
'FileExists' = ($FileExists = Test-Path ($FilePath = "\\$_\c$\Data\repository"))
'Size' = if($FileExists){ (Get-Item $FilePath).Length } else { $null }
}
}
(Get-Item $FilePath).Length won't get you far if C:\Data\repository is a directory, but you can substitute your own statement inside the if block
For the sake of my future coworkers I would probably avoid doing a one-liner like your example, and write multiple statements like so:
Get-Content c:\Users\Admin\Documents\Scripts\Serverlist.txt | ForEach-Object {
$ComputerName = $_
$FilePath = "\\$ComputerName\c$\Data\repository"
$FileExists = Test-Path $FilePath
if($FileExists){
$Size = Get-Item $FilePath | Select-Object -ExpandProperty Length
} else {
$Size = $null
}
New-Object psobject -Property #{
'ComputerName' = $ComputerName
'FileExists' = $FileExists
'Size' = $Size
}
}

Find next available computer name

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