Powershell Get-Content - variable File - powershell

I need to change the encoding of a file from UTF8-BOM to UTF8 different files where the date is different. I have a variable with the Data that I need but I have problems passing that variable to the Get-Content param.
$Today = Get-Date -Format "yyyyMMdd"
Get-Content 'D:\Folder_1\Test_file.'+$(${Today})+'.csv' | Set-Content -Encoding Ascii 'D:\Folder_2\Test_file.'+$(${Today})+'.csv'
The file names are "Test_file.20220923.csv" for example.
The error:
Get-Content : A positional parameter cannot be found that accepts argument '+20220923+.

'D:\Folder_1\Test_file.'+$(${Today})+'.csv' is an expression (a string concatenation via the + operator).
In order to pass an expression as an argument to a command, you must enclose it in (...), the grouping operator.[1]
Therefore (applies analogously to your Set-Content call):
Get-Content ('D:\Folder_1\Test_file.'+${Today}+'.csv')
Note that I've omitted the unnecessary use of $(...), the subexpression operator, which is typically only needed inside expandable (double-quoted) string ("..."), and then only for embedding expressions or commands (a variable reference such as $Today by itself can be embedded as-is).
The equivalent command using an expandable string:
Get-Content "D:\Folder_1\Test_file.${Today}.csv"
As for what you tried:
# WRONG: Passes *two* arguments.
Get-Content 'D:\Folder_1\Test_file.'+$(${Today})+'.csv'
Even though 'D:\Folder_1\Test_file.'+$(${Today})+'.csv' contains no spaces, PowerShell breaks this compound token into two arguments, verbatim D:\Folder_1\Test_file. and (for instance) verbatim +20220923+.csv.[2]
It is the latter, i.e. the extra positional argument causes Get-Content to report an error, because it only supports one positional argument (which binds to its -Path parameter).
[1] $(...) is often seen in practice instead, but $(...) is only ever needed in two cases: (a) to embed entire statement(s), notably loops and conditionals, in another statement, and (b) to embed an expression, command, or statement(s) inside "...", an expandable (interpolating) string. Just (...) is enough to pass a single command or expression as a command argument. While not likely, the unnecessary use of $(...) can have side effects - see this answer.
[2] In case you're wondering how a compound token such as 'D:\Folder_1\Test_file.'+${Today}+'.csv' is parsed and results in these two arguments, see this answer.

Related

New-MailboxExportRequest quote issue

I'm trying to get the following command into a program I am developing in PowerShell studio, I'm having some issues figuring out the escaping sequence here, and testing takes forever since I have to import the PST just to view the data :(
Consider the following command which works correctly from the shell...
New-MailboxExportRequest -Mailbox user#domain.com -Name JobName -IncludeFolders "#Inbox#/*","#SentItems#" -ContentFilter {(Received -gt "06/10/2022") -and (Sent -gt "06/10/2022")} -FilePath "\\Server\Folder\MyPST.pst"
I am using variables inside program designer, so my actual code looks like this...
New-MailboxExportRequest -Mailbox $mailbox -Name $jobname -IncludeFolders $IncludeFolders -ContentFilter {(Received -gt "$RecDate") -and (Sent -gt "$SentDate")} -FilePath "$FilePath"
Basically I need to get the first code sample working using variables, however the -IncludeFolders and -ContentFilter parameters require some escaping I can't seem to figure out. Any and all help is much appreciated.
tl;dr:
You need to enclose your -ContentFilter argument in "..." overall in order to support embedding variable values, which you can themselves enclose in embedded '...' quoting:
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
No special syntax considerations apply with respect to using a variable with -IncludeFolders: Just make sure that variable $IncludeFolders contains an array of strings identifying the target folders; applied to your example:
$IncludeFolders = '#Inbox#/*', '#SentItems#'
If the list of folders must be parsed from user input provided as a single string ($textbox5.text in this example) do the following to convert this single string to an array of names:
# If $textbox5.text contains string '#Inbox#/*, #SentItems#',
# the result is the same as above.
$IncludeFolders = ($textbox5.text -split ',').Trim()
Read on for background information and caveats re -ContentFilter.
Note:
The -ContentFilter information below also applies to other Exchange cmdlets that accept filters, such as Get-Recipient with its -Filter parameter.
It also applies analogously to the -Filter parameter used with AD (Active Directory) cmdlets such as Get-ADUser, although the situation there is complicated by the fact that the AD provider does support evaluation of PowerShell variables, but only in simple cases - see this answer.
New-MailboxExportRequest's -ContentFilter expects a string argument, not a script block ({ ... }), which you've used in your question.
While you can situationally get away with a script block, it is best avoided for two reasons:
When a script block is used as a string (converted to one), string interpolation (embedding the values of variables in the string) is not supported - a script block stringifies to its verbatim content (sans { and }).
Conceptually, use of a script block can give the mistaken impression that an arbitrary piece of PowerShell code may be passed, which is not the case: -ContentFilter supports a domain-specific syntax only that only emulates a limited set of PowerShell features, called OPath filter syntax.
Notably, evaluation of PowerShell variables is not supported - their values must be embedded in the string up front, by PowerShell, using string interpolation.
Therefore:
It's best to use a string argument with -ContentFilter to begin with.
Since you do need to embed variable values, you need string interpolation, via an expandable (double-quoted) string ("..."), inside of which you may quote values with '...' for syntactical convenience.
In case there is no need to embed variable values, use a verbatim (single-quoted) string ('...'), inside of which you may quote values with "...", as in your question. (In effect, this is the equivalent of the (ill-advised) use of { ... }).
Applied to your example: The following embeds the (stringified) values of variables $RecDate and $SentDate in your -ContentFilter argument:
# Note the use of "..." for the outer quoting, and
# '...' for the embedded quoting.
-ContentFilter "(Received -gt '$RecDate') -and (Sent -gt '$SentDate')"
Caveats:
If the values of the variables to embed themselves contain ', use escaped embedded "..." quoting instead; e.g., `"$var`"
If a value could contain ' and/or " and you don't know which, you'd have to use an extra layer of up-front string interpolation on the PowerShell side to escape one of them, depending on the embedded quoting character chosen, using $(...), the subexpression operator, using a -replace operation:
In this escaping you must technically satisfy the OPath filter syntax requirements; the linked help OPath topic suggests that, like in PowerShell, '' can be used to escape ' inside a '...' string; e.g.: -ContentFilter "Body -like '$($var -replace "'", "''")*'"
While, as stated, OPath filters do not support evaluating PowerShell variable references in general, the following automatic variables - which are conceptually constants - are recognized (the linked help topic calls them system values):
$true, $false
$null
Therefore, if you use "..." for the outer quoting (for string interpolation), the $ in these variables must be escaped with ` (the so-called backtick, PowerShell's escape character), to prevent PowerShell from replacing these variable references with their values up front:
`$true, `$false
`$null

How to list contents of a specific directory in powershell?

I'm rather baffled by Powershell in general. Very weird. Anyway, I've read:
Powershell's equivalent to Linux's: ls -al
so I now know how to list the contents of the current directory. But how can I list the contents of an arbitrary directory?
Specifically,
How do I type in the path I want to check?
in Windows, \ is the directory separator; but it's also an escape char in most languages. What do I do with it?
Do I need single-quotes? Double-quotes? No-quotes?
Where do I place the argument relative to the switches? After, like I'm used to, or rather before?
If I want to use an environment variable, or a powershell variable, as part of the path to list - how do I do that?
General PowerShell information and examples:
PowerShell-native commands, including user-authored ones that opt in, have standardized parameter syntax and binding rules, so the following, focused on Get-ChildItem, applies generally:
See the help topic for Get-ChildItem, which describes the command's purpose, syntax, and individual parameters, along with showing examples.
If you have offline help installed (true by default in Windows PowerShell; installable on demand via Update-Help in PowerShell (Core) 7+), you can also print the examples with Get-Help -Example Get-ChildItem | more
As for how to generally read the notation describing the supported parameters listed under the SYNTAX heading of cmdlet help topics, which PowerShell calls syntax diagrams, see the conceptual about_Command_Syntax help topic.
Offline, you can print the syntax diagrams for PowerShell-native commands with standard switch -? or by passing the command name to Get-Command -Syntax (Get-ChildItem -? or Get-Command -Syntax Get-ChildItem). To also access the help text offline, you may have to install local help first, as described above.
Examples:
# The following commands all pass "\" (the [current drive's] root dir)
# to the -Path parameter.
# Note:
# * "\" is NOT special in PowerShell, so it needs no escaping.
# PowerShell allows use of "/" instead even on Windows.
# * -Path arguments are interpreted as wildcard patterns, whether
# quoted or not. You may pass *multiple* paths / patterns.
# * Switch -Force - which, as all switches - can be placed anywhere
# among the arguments, requests that *hidden* items be listed too.
Get-ChildItem -Path \ -Force
Get-ChildItem \ -Force # ditto, with *positional* param. binding
'\' | Get-ChildItem # ditto, via the pipeline.
Get-ChildItem / -Force # ditto, with "/" as alternative to "\"
# To force interpretation as a *literal* path - which matters for
# paths that contain "[" chars. - use -LiteralPath.
# Here too you may pass *multiple* paths.
Get-ChildItem -LiteralPath \ -Force
# Quoting is needed for paths with spaces, for instance.
# Single-quoting treats the string *verbatim*:
Get-ChildItem 'C:\path\to\dir with spaces'
# Double-quoting *expands* (interpolates) variable references
# and subexpressions.
Get-ChildItem "$HOME\dir with spaces"
# A variable alone can be used as-is, without double-quoting, even
# if its value contains spaces.
Get-ChildItem $HOME
To answer your specific questions, for readers who come from POSIX-compatible shells such as Bash:
How do I type in the path I want to check?
Get-ChildItem offers two ways to specify one or more input paths, as most file-processing cmdlets in PowerShell do:
-Path accepts one or more names or paths that are interpreted as a wildcard pattern.
Note that - unlike in POSIX-compatible shells such as Bash - it does not matter whether the path is unquoted or not; e.g., Get-ChildItem -Path *.txt, Get-ChildItem "*.txt", and Get-ChildItem '*.txt' are all equivalent; more on that below (note the incidental omission of -Path in the latter two calls, which makes "*.txt" and '*.txt' bind positionally to the first positional parameter, -Path).
-LiteralPath accepts one or more names or paths that are assumed to refer to existing file-system items literally (verbatim).
Given that literal paths on Unix-like platforms usually do not contain * and ? characters and on Windows cannot, use of -LiteralPath for disambiguation is usually only necessary for paths that contain [ (and possibly also ]), given that PowerShell's wildcard pattern language also supports character sets and ranges (e.g. [ab] and [0-9]).
Binding to -LiteralPath via an argument requires explicit use of -LiteralPath, i.e. use of a named argument; e.g., Get-ChildItem -LiteralPath foo
However, supplying System.IO.FileInfo and/or System.IO.DirectoryInfo instances (such as output by (another) Get-ChildItem or Get-Item call) via the pipeline DOES bind to -LiteralPath, as explained in this answer.
in Windows, \ is the directory separator; but it's also an escape char in most languages. What do I do with it?
In PowerShell \ is not an escape character, so \ instances are treated as literals and do not require escaping; it is `, the so-called backtick that serves as the escape character in PowerShell - see the conceptual about_Special_Characters help topic.
Note, however, that PowerShell generally allows you to use \ and / in paths interchangeably, so that, e.g., Get-ChildItem C:/Windows works just fine.
Where do I place the argument relative to the switches? After, like I'm used to, or rather before?
Note:
All parameters have names in PowerShell - that is, there is no POSIX-like distinction between options (e.g. -l and operands (value-only arguments, such as the / in ls -l /).
A command may declare parameters that may also be passed by value only, positionally, meaning that prefixing the value with the parameter name is then optional (e.g., Get-Path / as shorthand for Get-Path -Path /).
Only parameters that require a value (argument) can potentially be passed as values only - depending on the parameter declaration of the target command - in which case their order matters:
Value-only arguments are called positional arguments, and they bind in order to those parameters of the target command that are declared as positional, if any - see this answer for how to discover which of a given command's parameters are positional ones.
Prefixing a value by its target parameter (e.g., -LiteralPath /some/path) makes it a named argument.
A switch (flag) in PowerShell, such as -Force, is a special parameter type - Boolean in nature - that by definition requires passing it as a named argument, typically without a value (though you can attach a value, e.g. -Force:$true or -Force:$false - note that : is then required to separate the parameter name from its value; see this answer for details).
As an aside: Unlike POSIX-compliant utilities, PowerShell does not support parameters with optional values of any other type - see this answer.
Since PowerShell allows named arguments to be specified in any order, the implication is that you're free to place by-definition-named switch arguments such as -Force anywhere on the command line.
In short: Order only matters among positional (unnamed) arguments in PowerShell.
See the conceptual about_Parameters help topic for more information.
Do I need single-quotes? Double-quotes? No-quotes?
A path (parameter value in general) needs quoting:
if it contains PowerShell metacharacters, notably spaces; e.g. C:\path\to\foo needs no quoting, whereas C:\path with spaces\to\foo does.
if it starts with a subexpression ($(...)), in which case you need double-quoting, i.e. "..." (see below) - though you may choose to always use "..."-quoting for paths involving subexpressions or variable references.
Note that PowerShell has no concept that is the equivalent of the so-called shell expansions in POSIX-compatible shells such as Bash; therefore, whether a given argument is quoted or not makes no semantic difference (assuming it doesn't require quoting); as noted above, *.txt, '*.txt' and "*.txt" are all equivalent, and it is the target command, not PowerShell itself, that interprets the pattern - see this answer for more information.
If quoting is needed:
Verbatim (single-quoted) strings ('...') treat their content verbatim
Expandable (double-quoted) strings ("...") perform string interpolation ("expand" embedded variables and subexpressions, i.e replace them with their values).
If I want to use an environment variable, or a powershell variable, as part of the path to list - how do I do that?
To use such variables as-is, no quoting is needed (even if the values contain spaces):
# PowerShell variable:
Get-ChildItem -LiteralPath $HOME
# Environment variable, e.g. on Windows:
Get-ChildItem -LiteralPath $env:USERPROFILE
To make a variable (or expression) part of a larger string, embed it in an expandable (double-quoted) string ("..."); e.g.:
Get-ChildItem -LiteralPath "$HOME/Desktop"
Note:
Embedding the output from expressions or commands requires use of $(...), the subexpression operator; e.g. Get-ChildItem "$(Get-Variable -ValueOnly Home)/Desktop"; for a complete overview of PowerShell's expandable strings (string interpolation), see this answer.
Situationally, such as in the example above, omitting the "..." quoting would work too - see this answer for more information.
Additionally and alternatively, PowerShell allows you to use (...), the grouping operator to pass the result of arbitrary expressions and commands as arguments; the following is the equivalent of the command above:
Get-ChildItem -LiteralPath ($HOME + '/Desktop')
Alternatively, using the Join-Path cmdlet:
Get-ChildItem -LiteralPath (Join-Path $HOME Desktop)

add-member weirdness with type assignment

In the code below, why does the [int] type assignment for name1 get ignored and is actually treated as part of the value? Bug?
$name1 = 4
$name2 = 4
$name3 = 4
$myObject = New-Object System.Object
$myObject | Add-Member -type NoteProperty -name name1 -Value [int]$name1
$myObject | Add-Member -type NoteProperty -name name2 -Value $($name2 -as [int])
$myObject | Add-Member -type NoteProperty -name name3 -Value $([int]$name3)
$myObject
Output:
name1 name2 name3
----- ----- -----
[int]4 4 4
Powershell version:
get-host | select-object version
Version
-------
5.1.19041.1023
There's good information in the existing answers, but let me attempt a systematic overview:
tl;dr
In order to pass the output from an expression (e.g. [int] $name1), or (nested) command (e.g. Get-Date -Format yyyy) as an argument to a command (e.g. Add-Member), enclose it in (...), the grouping operator:
Add-Member -type NoteProperty -name name1 -Value ([int] $name1)
By contrast, $(...), the subexpression operator is typically not needed in this scenario, and its use can have side effects - see this answer.
In short: (Outside of expandable strings, "..."), you only ever need $(...) to either enclose a language statement (such as an if statement or foreach loop) or multiple statements (any mix of commands, expressions, and language statements separated with ;).
PowerShell's parsing modes:
PowerShell has two fundamental parsing modes:
argument mode, which works like shells.
In argument mode, the first token is interpreted as a command name (the name of cmdlet, function, alias, or the name of path of an external executable or .ps1 script), followed by a whitespace-separated list of arguments, where strings may be unquoted[1] and arguments composed of a mix of literal parts and variable references are typically treated like expandable strings (as if they were enclosed in "...").
expression mode, which works like programming languages, where strings must be quoted, and operators and language statements such as assignments, foreach and while loops, casts can be used.
The conceptual about_Parsing provides an introduction to these modes; in short, it is the first token in a given context that determines which mode is applied.
A given statement may be composed of parts that are parsed in either mode, which is indeed what happens above:
Because your statement starts with a command name (Add-Member), it is parsed in argument mode.
(...) forces a new parsing context, which in the case at hand ([int] $name1) is parsed in expression mode, due to starting with [).
What is considered a metacharacter (a character with special, syntactic meaning) differs between the parsing modes:
[ and = are special only in expression mode, not in argument mode, where they are used verbatim.
Conversely, a token-initial # followed by a variable name is only special in argument mode, where it is used for parameter splatting.
Compound argument [int]$name1 is therefore treated as if it were an expandable string, and results in verbatim string [int]4.
Some expressions do not require enclosing in (...) when used as command arguments (assume $var = 'Foo'):
A stand-alone variable reference (e.g. Write-Output $var or Write-Output $env:OS)
Property access on such a reference (e.g. Write-Output $var.Length)
Method calls on such a reference (e.g. Write-Output $var.ToUpper())
Note that these arguments are passed with their original data type, not stringified (although stringification may be performed by the receiving command).
Pitfalls:
You sometimes need to use "..." explicitly in order to suppress property-access interpretation and have a . following a variable reference be interpreted verbatim (e.g. Write-Output "$var.txt" in order to get verbatim foo.txt).
If you use $(...) as part of a compound argument without explicit "..." quoting, that argument is broken into multiple arguments if the $(...) subexpression starts the argument (e.g., Write-Output $('a' + 'b')/c passes two arguments, verbatim ab and /c, whereas Write-Output c/$('a' + 'b') passed just one, verbatim c/ab).
Similarly, mixing quoted and unquoted strings to form a single argument only works if the argument starts with an unquoted token (e.g., Write-Output One"$var"'$Two' works as expected and yields verbatim OneFoo$Two, but Write-Output 'One'"$var"'$Two' is passed as three arguments, verbatim One, Foo, and $Two).
In short:
The exact rules for how arguments are parsed are complex:
This answer summarizes the rules for unquoted arguments.
This answer summarizes mixing quoted and unquoted strings in a single argument
This answer) (bottom section) summarizes PowerShell's string literals in general.
To be safe, avoid use of $(...) outside "..." and avoid mixing quoting styles in a single string argument; either use a (single) "..." string (e.g. Write-Output "$(Split-Path $PROFILE)/foo.txt" or ) or string concatenation in an expression (Write-Output ('One' + $var + '$Two')
[1] Assuming they contain neither spaces nor any of PowerShell's metacharacters (see this answer). While quoting typically takes the form of enclosing the entire argument in single or double-quotes, as appropriate (e.g. 'foo bar', "foo $var"), it is also possible to quote (escape) individual characters (e.g. foo` bar), using the backtick (`), PowerShell's escape character.
From the about_Parsing help file:
Argument mode
When parsing, PowerShell first looks to interpret input as an expression. But when a command invocation is encountered,
parsing continues in argument mode.
Argument mode is designed for parsing arguments and parameters for
commands in a shell environment. All input is treated as an expandable
string unless it uses one of the following syntaxes:
...
You can enclose the code in a subexpression ($(...)) to avoid PowerShell treating [int]$name1 as an expandable string, as you've already found :-)
This is by design. You need to wrap in parentheses in order to evaluate the expression before passing it into the function.
Its the same if you try to put a function call there for instance:
$myObject | Add-Member -type NoteProperty -name name1 -Value Write-Host
returns as:
name1
-----
Write-Host
And Write-Host [string]2 returns [string]2 not 2

Editing Powershell Object

I'm using powershell to run a command like so:
$getlist=rclone sha1sum remote:"\My Pictures\2009\03" --dry-run
Write-Output $getlist
that outputs a object with the results. Problem being I only want the first column of those results. I've tried things like custom-format --Depth 1 and the other *-format commands but they don't work on this object??
that outputs a object with the results
While that is technically true, it is more specifically an [object[]]-typed array of lines ([string] instances) that assigning the stream of output lines - produced by the external rclone program - to a PowerShell variable implicitly created. (Arrays created by PowerShell are [object[]]-typed, even if all the elements are of the same type, such as [string] in this case).
PowerShell fundamentally only "speaks text" when communicating with external programs.
Therefore, to extract substrings from these lines you must perform text parsing, as implied by AdminOfThings' comment on the question.
A simplified approach is to use the unary form of the -split operator:
# Simulate lines input whose first whitespace-separated token is to
# be extracted.
$getlist = 'foo bar baz', 'more stuff here'
$getlist.ForEach({ (-split $_)[0] })
The above yields:
foo
more
zett42's helpful answer shows a simpler alternative that relies on the -replace operator's (among others) ability to operate directly on each element of an array-valued LHS.
However, the -split approach is useful if you want to extract multiple column values.
If you don't need / want to capture all of the external program's (rclone's) output in memory first, you can use streaming processing in the pipeline, via the ForEach-Object cmdlet:
'foo bar baz', 'more stuff here' | ForEach-Object { (-split $_)[0] }
Note: While slightly slower than collecting all lines in memory up front, the advantage of a pipeline-based approach is reduced memory load: only the extracted substrings are kept in memory (if assigned to a variable).
You can use a regular expression to remove the undesired parts of the output:
$getlist = $getlist -replace '\s.*'
When a PowerShell operator such as -replace is applied to a collection, it will be applied to each element individually, creating a new array that stores the results (see Substitution in a collection).
The regular expression removes everything from the first whitespace up to the end of the string.
RegEx breakdown:
\s - a single whitespace character like space and tab
.* - any character, zero or more times

Powershell indexing for creating user logon name

I'm trying to make a user creation script for my company to make things more automated.
I want the script to take the Firstname + Lastname[0] to make the users logon name, but i can't get the syntax right,
I have tried writing {} and () but no luck there.
that's the original peace from my script
New-ADuser...........-UserPrincipalName $fname+$lname[0]
any tips?
Gabriel Luci's helpful answer provides an effective solution and helpful pointers, but it's worth digging deeper:
Your problem is that you're trying to pass expression $fname+$lname[0] as an argument, which requires enclosing (...):
New-ADuser ... -UserPrincipalName ($fname+$lname[0])
PowerShell has two distinct parsing modes, and when a command (such as New-ADUser) is called, PowerShell operates in argument mode, as opposed to expression mode.
Enclosing an argument in (...) forces a new parsing context, which in the case of $fname+$lname[0] causes it to be parsed in expression mode, which performs the desired string concatenation.
In argument mode, unquoted arguments are implicitly treated as if they were enclosed in "...", i.e., as expandable strings under the following circumstances:
If they don't start with (, #, $( or #(.
If they either do not start with a variable reference (e.g., $var) or do start with one, but are followed by other characters that are considered part of the same argument (e.g., $var+$otherVar).
Therefore, $fname+$lname[0] is evaluated as if "$fname+$lname[0]" had been passed:
The + become part of the resulting string.
Additionally, given that inside "..." you can only use variable references by themselves (e.g., $fname), not expressions (e.g., $lname[0]), $lname[0] won't work as intended either, because the [0] part is simply treated as a literal.
Embedding an expression (or a command or even multiple expressions or commands) in "..." requires enclosing it in $(...), the subexpression operator, as in Gabriel's answer.
For an overview of PowerShell's string expansion rules, see this answer.
The following examples use the Write-Output cmdlet to illustrate the different behaviors:
$fname = 'Jane'
$lname = 'Doe', 'Smith'
# WRONG: Same as: "$fname+$lname[0]", which
# * retains the "+"
# * expands array $lname to a space-separated list
# * treats "[0]" as a literal
PS> Write-Output -InputObject $fname+$lname[0]
Jane+Doe Smith[0]
# OK: Use an expression via (...)
PS> Write-Output -InputObject ($fname+$lname[0])
JaneDoe
# OK: Use an (implicit or explicit) expandable string.
PS> Write-Output -InputObject $fname$($lname[0]) # or: "$fname$($lname[0])"
JaneDoe
# OK: Use an intermediate variable:
PS> $userName = $fname + $lname[0]; Write-Output -InputObject $userName
Use a string for the UserPrincipalName, with the variables in the string:
New-ADuser -UserPrincipalName "$fname$($lname[0])"
PowerShell can usually figure out when you put a variable inside a string. When it can't, like in the case of $lname[0], you enclose it in $().
This is called "variable expansion" (other languages, like C#, call it "string interpolation"). Here's a good article that describes it in more detail: https://powershellexplained.com/2017-01-13-powershell-variable-substitution-in-strings/
i just saw the answers and a minute before i realized that i should actually set it up as another variable, $logon = $fname+lname[0]
and pass it as -userPrincipalName $logon.
Thanks for the help, you guy are the best!