I'm pretty new when it comes to scripting with powershell (or in general whe it comes to scripting). The problem that i have, is that i got a bunch of variables i want to output in one line. Here is not the original but simplified code:
$a = 1
$b = 2
$c = $a; $b;
Write-output $c
The output looks like this:
1
2
You may guess how i want the output to look like:
12
I've searched the net to get a solution but nothing seem to work. What am i doing wrong?
Right now you're only assigning $a to $c and then outputting $b separately - use the #() array subexpression operator to create $c instead:
$c = #($a; $b)
Then, use the -join operator to concatenate the two values into a single string:
$c -join ''
You can make things easier on yourself using member access or Select-Object to retrieve property values. Once the values are retrieved, you can them manipulate them.
It is not completely clear what you really need, but the following is a blueprint of how to get the desired system data from your code.
# Get Serial Number
$serial = Get-CimInstance CIM_BIOSElement | Select-Object -Expand SerialNumber
# Serial Without Last Digit
$serialMinusLast = $serial -replace '.$'
# First 7 characters of Serial Number
# Only works when serial is 7 or more characters
$serial.Substring(0,7)
# Always works
$serial -replace '(?<=^.{7}).*$'
# Get Model
$model = Get-CimInstance Win32_ComputerSystem | Select-Object -Expand Model
# Get First Character and Last 4 Characters of Model
$modelSubString = $model -replace '^(.).*(.{4})$','$1$2'
# Output <model substring - serial number substring>
"{0}-{1}" -f $modelSubString,$serialMinusLast
# Output <model - serial number>
"{0}-{1}" -f $model,$serial
Using the syntax $object | Select-Object -Expand Property will retrieve the value of Property only due to the use of -Expand or -ExpandProperty. You could opt to use member access, which uses the syntax $object.Property, to achieve the same result.
If you have an array of elements, you can use the -join operator to create a single string of those array elements.
$array = 1,2,3,'go'
# single string
$array -join ''
The string format operator -f can be used to join components into a single string. It allows you to easily add extra characters between the substrings.
Currently, I'm trying to add a function to my powershell script with the following goal:
On a computer that isn't added to the domain (yet), have it search a local AD server (Not azure) for the next available name based off the user's input.
I have tried and failed to use arrays in the past, and I want to use the Get-ADComputer cmdlet in this, but I'm not sure how to implement it.
$usrinput = Read-Host 'The current PC name is $pcname , would you like to rename it? (Y/N)'
if($usrinput -like "*Y*") {
Write-Output ""
$global:pcname = Read-Host "Please enter the desired PC Name"
Write-Output ""
$userinput = Read-Host "You've entered $pcname, is this correct? (Y/N)"
if($usrinput -like "*N*") {
GenName
#name of the parent function
}
Write-Output ""
The above code is part of a larger script that parses a computer name and assigns it to the correct OU in the end.
Our naming scheme works like this: BTS-ONE-LAP-000
So it is: Department - Location - Device Type - Device Count
The code will then take the first part "BTS-ONE" and parse it for the correct OU it should go to, and then assign it using the Add-Computer cmdlet. It will also rename the machine to whatever the user typed in ($pcname).
So, before it parses the name, I'd like it to search all current names in AD.
So, the user can type in: "BTS-ONE-LAP" and it will automatically find the next available Device Count, and add it to the name. So, it will automatically generate "BTS-ONE-LAP-041".
Added Note:
I've used Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | FT Name and the output is
Name
----
BTS-ONE-LAP-001
BTS-ONE-LAP-002
BTS-ONE-LAP-006
BTS-ONE-LAP-007
BTS-ONE-LAP-009
BTS-ONE-LAP-010
BTS-ONE-LAP-022
BTS-ONE-LAP-024
BTS-ONE-LAP-025
BTS-ONE-LAP-028
BTS-ONE-LAP-029
BTS-ONE-LAP-030
BTS-ONE-LAP-031
BTS-ONE-LAP-032
BTS-ONE-LAP-034
BTS-ONE-LAP-035
BTS-ONE-LAP-036
BTS-ONE-LAP-037
BTS-ONE-LAP-038
BTS-ONE-LAP-039
BTS-ONE-LAP-040
BTS-ONE-LAP-041
BTS-ONE-LAP-050
BTS-ONE-LAP-051
I don't know how to parse this so the code knows that BTS-ONE-LAP-003 is available (I'm terrible with arrays).
$list = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"' | Sort-Object Name[-1])
$i = 1
$found = $false
Foreach($Name in $list.Name)
{
while($i -eq [int]$Name.Split("-")[3].Split("-")[0])
{
$i++
}
}
$i
The above code will go through each name in the list, and will stop when it discovers say the 3rd computer in the set is NOT computer #3.
Example:
BTS-ONE-LAP-001 | $i = 1
BTS-ONE-LAP-002 | $i = 2
BTS-ONE-LAP-006 | $i = 3
It split BTS-ONE-LAP-006 to be 006, and convert it to an integer, making it 6.
Since 6 does not equal 3, we know that BTS-ONE-LAP-003 is available.
Another way could be to create a reusable function like below:
function Find-FirstAvailableNumber ([int[]]$Numbers, [int]$Start = 1) {
$Numbers | Sort-Object -Unique | ForEach-Object {
if ($Start -ne $_) { return $Start }
$Start++
}
# no gap found, return the next highest value
return $Start
}
# create an array of integer values taken from the computer names
# and use the helper function to find the first available number
$numbers = (Get-ADComputer -Filter 'Name -like "BTS-ONE-LAP-*"') |
ForEach-Object { [int](([regex]'(\d+$)').Match($_.Name).Groups[1].Value) }
# find the first available number or the next highest if there was no gap
$newNumber = Find-FirstAvailableNumber $numbers
# create the new computername using that number, formatted with leading zero's
$newComputerName = 'BTS-ONE-LAP-{0:000}' -f $newNumber
Using your example list, $newComputerName would become BTS-ONE-LAP-003
Note that not everything a user might type in with Read-Host is a valid computer name. You should add some checks to see if the proposed name is acceptable or skip the proposed name alltogehter, since all your machines are named 'BTS-ONE-LAP-XXX'.
See Naming conventions in Active Directory for computers, domains, sites, and OUs
I am trying to automate few of the daily health check tasks using PowerShell.
I would like to achieve the following (Step by Step), though I have a partial success in few,
Extract content of text (Log) file located at shared location (I have succeeded) by defining $Path = Set-Location ... etc.,
Send email (succeeded) to mail box, by defining
Real help I need is here,
I want Headings to be appended in Email, along with original extracted text from Step 1,
For ex..
Original Text looks like this (extracted from text file at shared location):
01-01-2018 Number of Successful object - 1
I would like to add the header for this on email, like
date Description Number of Objects
01-01-2018 Successful objects 1
Assuming the content from the log file gathered in Step 1 is a string array of log entries where every string has a format similar to '01-01-2018 Number of Successful object - 1 '
In this example I call that array $logEntries
# create an array to store the results objects in
$result = #()
# loop through this array of log entries
$logEntries | ForEach-Object {
# Here every text line is represented by the special variable $_
# From your question I gather they all have this format:
# '01-01-2018 Number of Successful object - 1 '
if ($_ -match '(?<date>\d{2}-\d{2}-\d{4})\s+(?<description>[^\-\d]+)[\s\-]+(?<number>\d+)\s*$') {
# Try to get the 'Succesful' or 'Failed' (??) text part out of the description
$description = ($matches['description'] -replace 'Number of|object[s]?', '').Trim() + ' object'
if ([int]$matches['number'] -ne 1) { $description += 's' }
# add to the result array
$result += New-Object -TypeName PSObject -Property ([ordered]#{
'Date' = $matches['date']
'Description' = $description
'Number of Objects' = $matches['number']
})
}
}
# now decide on the format of this result
# 1) as plain text in tabular form.
# This looks best when used with a MonoSpaced font like Courier or Consolas.
$result | Format-Table -AutoSize | Out-String
# or 2) as HTML table. You will have to style this table in your email.
# You may include a stylesheet using the '-CssUri' parameter, but inserting
# it in a nicely drawn-up HTML template will give you more creative freedom.
$result | ConvertTo-Html -As Table -Fragment
p.s. because the PSObject has [ordered] properties, this needs PowerShell version 3.0 or better.
I have a .csv file that contains well over 1000 entries of former employees of the company I work for, and I am attempting to split it up by employee into a format that I can insert into an email template. To exacerbate things, many (but not all) of the employees have more than one entry, and the .csv contains a considerable amount of information that is completely unnecessary for the people I will be sending the emails to. Ideally I would like to exclude these columns, but I am having quite a bit of difficulty with it. It seems to me that format-table will be the cmdlet I will be needing, but I am not 100% certain. Thus far I have tried:
import-csv C:\filepath\xx.csv | format-table -groupby (key value)
which does return the information organized in the way I would like, however I do not know how to split the information from there so I can only send the information for the specific employee. This also includes quite a bit of extraneous information, as previously mentioned. I have also tried:
import-csv C:\filepath\xx.csv | format-table -groupby (key value) -property (properties I want,separated by commas)
However, this is still returning the same extraneous information. I also tried using a foreach loop to iterate through the .csv, store the information I want in variables, then store those variables in an array, then pipe that array into format-table, which results in a mess. I have also tried putting the .csv into a hashtable grouped by the key value I want, however that results in the rest of the information being put into a string. I believe I should be able to split this string using regex, however I am not at all familiar with regex and have no idea where to start with that. I'm pretty well stumped at this point. Any help would be greatly appreciated.
EDIT
After following #4c74356b41's suggestion (for which I am very grateful), I am able to output a list of tables with only the pertinent information. However, I am still unable to split this list of tables into the individual tables I need. I currently have a .txt file which I am using as a template, which I would like to add the table for the individual users to. I have so far had success with using get-content to retrieve the contents of the .txt, -replace to replace several other fields on the template from information in the original .csv (eg name, manager, etc), and then out-file to store the edited template into a temp file, which is then saved as a draft to outlook. I attempted to add another foreach loop inside the previous foreach loop to add the tables into the template, but that is returning templates with the line 'Microsoft.PowerShell.Commands.Internal.Format.FormatStartData' in place of the table. It is also creating a draft for each entry rather than just one for each unique user id. Here is all the code I have so far:
$olFolderDrafts = 16
$ol = New-Object -comObject Outlook.Application
$ns = $ol.GetNameSpace("MAPI")
$file= import-csv H:\filepath\term_rep.csv
$data= import-csv H:\filepath\term_rep.csv | select-object "Inactive Emp Name","User ID","Loc","Equipment Descr","Tag Number","Serial Number"
$table= $data | Format-Table -GroupBy "User ID"
foreach($i in $file)
{$user = $i | select-object -expandproperty "Inactive Emp Name"
$uid= $i | select-object -expandproperty "User ID"
$manager = $i | Select-Object -expandproperty "Manager"
$loc= $i | select -ExpandProperty "Loc"
$tagnum= $i | select -ExpandProperty "Tag Number"
$sernum= $i | select -ExpandProperty "Serial Number"
$path="C:\filepath\term_email.txt"
$newpath = [system.io.path]::GetTempFileName()
(Get-content $path) -replace "USER_NAME",$user `
-replace "USER_ID",$uid | out-file $newpath
foreach($id in $table){
(Get-Content $newpath) -replace "EQUIPMENT_LIST", $id | out-file $newpath
}
$subject="Equipment for user $uid"
$body= get-content $newpath
$mail = $ol.CreateItem(0)
$mail.To = $manager
$mail.CC = $null
$mail.Subject = $subject
$mail.Body = $body -join "`n"
$mail.save()}
To reduce noise and duplicates I would find a column that is specific to a user, such as UserName, or Email. Then group by that, and select the first item in each group. Then you could pipe to Select to reduce noise from extraneous columns. Something like:
import-csv C:\filepath\xx.csv | Group UserName | ForEach{$_.Group[0]} | Select FirstName,LastName,Email,UserName,SeparationDate
Then you can do with it what you want... Pipe to another ForEach loop to work with each record one at a time, or pipe it to Export-CSV to generate a new CSV with reduced clutter that you could work with. You could even have PowerShell create the emails for you, depending on how fancy you want to get.
If you need further help please update your question with an example of what your desired output to be, or what exactly you are trying to accomplish with each entry.
Edit: Ok, so when it comes down to it you want a insert a table of things for any records matching a specific user into an email, so that you can get their stuff back from their manager after they're separated from the company. Cool, we can do that. To start off with, you're using the Outlook ComObject, and generating your email that way. Awesome, I've done it myself, and you can do some great stuff that way! I highly recommend using HTML here. It makes the email look more professional, and lets us insert a nice looking table into the email instead of some formatted text that will look funny due to spacing once its in the email.
So, let's back up from the issue just a hair, and redo your email template. Pop open MS Word and open your template. Now, go to File and Save As. Select 'Web Page, Filtered' as your document type, and name it the same thing you already had.
So, rather than loading the list twice, then exporting, and blah, blah, blah, we're going to shorten things a little. We load your CSV once. For that matter, we don't need to read the body of the email once for each user, so let's just load it up one time here before the loop as well. Then we pipe the User ID field to Select-Object using the -Unique switch, so that we only get one email generated per user. So, the script up to there looks like this:
$olFolderDrafts = 16
$ol = New-Object -comObject Outlook.Application
$ns = $ol.GetNameSpace("MAPI")
$file= import-csv H:\filepath\term_rep.csv
$path="C:\filepath\term_email.txt"
$body = (Get-content $path) -join "`n"
foreach($i in ($file.'User ID'|Select -Unique))
{
Now, there's no need to assign all of those variables like you were, so I'm skipping that part. So we'll find the first instance of the current user, and save that to a variable.
$User = $File|Where{$_.'User ID' -eq $i}|Select -First 1
Once we've got that we will find all of their records in the list, select only the properties that you are interested in, and convert the output to an HTML table using ConvertTo-HTML.
$Equip = $File|Where{$_.'User ID' -eq $i}|select-object "Inactive Emp Name","User ID","Loc","Equipment Descr","Tag Number","Serial Number"|ConvertTo-Html -Property '*' -As Table -Fragment
Now that is a pretty long line, but most of it is just selecting the right properties. The magic happens in the last bit where we tell it to convert the data to a HTML. Specifically we want everything (specified by -Property '*'), that we want it to be converted as a table, and that this is just a fragment, so it doesn't try to put all the preceding and following tags in there (so no <HTML> and </HTML> type tags since it's being injected into the middle of existing HTML.)
Then we do the replaces, make the email, assign the properties, yada, yada, yada, you pretty much already had all this...
$HTMLbody = $body -replace "USER_NAME",$user.'Inactive Emp Name' -replace "USER_ID",$user.'User ID' -replace "EQUIPMENT_LIST", $Equip
$subject="Equipment for user " + $User.'User ID'
$mail = $ol.CreateItem(0)
$mail.To = $User.Manager
$mail.Subject = $subject
$mail.HTMLBody = $HTMLbody
$mail.save()
}
Save it, and done! Loop to the next user, and repeat. So put that together and you get:
$olFolderDrafts = 16
$ol = New-Object -comObject Outlook.Application
$ns = $ol.GetNameSpace("MAPI")
$file= import-csv H:\filepath\term_rep.csv
$path="C:\temp\email.htm"
$body = (Get-content $path) -join "`n"
foreach($i in ($file.'User ID'|Select -Unique))
{
$User = $File|Where{$_.'User ID' -eq $i}|Select -First 1
$Equip = $File|Where{$_.'User ID' -eq $i}|select-object "Inactive Emp Name","User ID","Loc","Equipment Descr","Tag Number","Serial Number"|ConvertTo-Html -Property '*' -As Table -Fragment
$HTMLbody = $body -replace "USER_NAME",$user.'Inactive Emp Name' -replace "USER_ID",$user.'User ID' -replace "EQUIPMENT_LIST", $Equip
$subject="Equipment for user " + $User.'User ID'
$mail = $ol.CreateItem(0)
$mail.To = $User.Manager
$mail.Subject = $subject
$mail.HTMLBody = $HTMLbody
$mail.save()
}
Say your csv has some columns: name, surname, bla-bla, bla-bla, bla-bla.
$data = import-csv C:\filepath\xx.csv | select name, surname
$data | format-table -groupby (key value)
I would like to do an Active Directory search using PowerShell to discover if the username I want to create is already in use,. If it is already in use I want the script to add the following number at the and of the user name.
Import-Module ActiveDirectory
$family= Mclaren
$first= Tony
#This part of the script will use the first 5 letters of $family and the first 2 letters of $first and join them together to give the $username of 7 letters
$username = $family.Substring(0, [math]::Min(5, $family.Length)) + $first.Substring(0, [math]::Min(2, $first.Length))
The user name will look like "mclarto" base on that (username
take the 5 first letters of the family name plus 2 charof the firstname)
a seach is done in AD.
If there is no result, "mclarto" will be taken as $username without
any number at the end.
If the search find other users with the same username, the
username should take the following number, in this case it would be
"mclarto1".
If "mclarto1" already exist then "mclarto2" should be use and so on.
I think this will get you close, it uses the ActiveDirectory module.
Import-Module ActiveDirectory
$family = "Mclaren*"
# Get users matching the search criteria
$MatchingUsers = Get-ADUser -Filter 'UserPrincipalName -like $family'
if ($MatchingUsers)
{
# Get an array of usernames by splitting on the # symbol
$MatchingUsers = $MatchingUsers | Select -expandProperty UserPrincipalName | %{($_ -split "#")[0]}
# loop around each user extracting just the numeric part
$userNumbers = #()
$MatchingUsers | % {
if ($_ -match '\d+')
{
$userNumbers += $matches[0]
}
}
# Find the maximum number
$maxUserNumber = ($userNumbers | Measure-Object -max).Maximum
# Store the result adding one along the way (probably worth double checking it doesn't exist)
$suggestedUserName = $family$($maxUserNumber+1)
}
else
{
# no matches so just use the name
$suggestedUserName = $family
}
# Display the results
Write-Host $suggestedUserName