I am trying to get the real names of each user in an AD group, I have created it to the point of getting the output to show the User IDs of each member in the group but then trying to do a net user $_ /domain on each user in the .txt file it gives me errors like there is nothing there. Before the onslaught of "why dont you just use Get-AD*" I understand that its out there, but we cannot install that module here. I need something other people can use without installing things.
I have tried a few ways to approach this and the below code is the part of the script that doesnt seem to be working correctly. I have the other way I tried it commented out but left it there as another possible starting point.
function Return-DropDown {
$Choice = $DropDown.SelectedItem.ToString()
if ($Choice)
{
net group $Choice /domain > C:\Temp\RawOut.txt
$UserID = Get-Content 'C:\Temp\RawOut.txt'
$UserID[($UserID.IndexOf('-------------------------------------------------------------------------------') + 1) .. ($UserID.IndexOf('The command completed successfully.') -1)] > C:\Temp\RawIDs.txt
Start C:\Temp\RawIDs.txt
Remove-Item C:\Temp\RawOut.txt
Get-Content -path C:\Temp\RawIDs.txt | ForEach-Object {net user $_ /domain | findstr "Full Name"} >> C:\Temp\$Choice+RealNames.txt
#$RealNames = Get-Content -path C:\Temp\RawIDs.txt
#ForEach ($_ in $RealNames)
#{
# net user $_ /domain >> C:\Temp\$Choice+1.txt
# }
}
}
What I am getting back is:
net : The syntax of this command is:
At C:\Users\MyID\OneDrive - MyCompany\AD Group DropDown.ps1:34 char:64
+ ... path C:\Temp\RawIDs.txt | ForEach-Object {net user $_ /domain} >> C:\ ...
+ ~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (The syntax of this command is::String) [], RemoteException
+ FullyQualifiedErrorId : NativeCommandError
NET USER
[username [password | *] [options]] [/DOMAIN]
username {password | *} /ADD [options] [/DOMAIN]
username [/DELETE] [/DOMAIN]
username [/TIMES:{times | ALL}]
username [/ACTIVE: {YES | NO}]
Let me know any advice for getting this function to work.
Thank you to #LotPings for helping me out with finding a solution.
I discovered (through looking at a post LotPings commented) that I was getting the output in a format that was causing an error. My txt file was formatted:
User1 User2 User3
User4 User5 User6
...
And it was trying to take the full line User1 User2 User3 as the variable. Which was obviously not working. with using a -replace and -split to get the format right for my variable and it's working now.
function Return-DropDown {
$Choice = $DropDown.SelectedItem.ToString()
if ($Choice)
{
net group $Choice /domain > C:\Temp\RawOut.txt
$UserID = Get-Content 'C:\Temp\RawOut.txt'
$UserID[($UserID.IndexOf('-------------------------------------------------------------------------------') + 1) .. ($UserID.IndexOf('The command completed successfully.') -1)] > C:\Temp\RawIDs.txt
#Start C:\Temp\RawIDs.txt
Remove-Item C:\Temp\RawOut.txt
$results = Get-Content -path C:\Temp\RawIDs.txt
foreach($result in $results) {
($result -replace '\s+',',') -split ',' | ? { $_ } >> 'C:\temp\users.txt'
}
Get-Content -path C:\Temp\users.txt | ForEach-Object {net user $_ /domain | findstr "Full Name"} >> C:\Temp\$Choice.txt
Remove-Item C:\Temp\users.txt
Remove-Item C:\Temp\RawIDs.txt
Start C:\Temp\$Choice.txt
}
}
Related
I have a powershell script to retrieve the most recent login for guest users. It works fine.
(Based on https://gallery.technet.microsoft.com/office/Get-Guest-User-Last-Login-39f8237e)
$startDate = "{0:yyyy-MM-dd}" -f (get-date).AddDays(-365) #look 1 year back (not sure what the maximum is, but 1 year seems to work)
$endDate = "{0:yyyy-MM-dd}" -f (get-date) #current date.
$externalUserExtention = "*#EXT#*"
$filePath="c:\temp\guest_user_last_logins.txt"
function logtoText($filePath, $msg) {
$msg >> $filepath;
}
function find_last_login_date($user) {
$lastLoginDate = Search-UnifiedAuditLog -UserIds $user.UserPrincipalName -StartDate $startDate -EndDate $endDate| Foreach-Object {$_.CreationDate = [DateTime]$_.CreationDate; $_} | Group-Object UserIds | Foreach-Object {$_.Group | Sort-Object CreationDate | Select-Object -Last 1} | Select CreationDate
Write-Host "User " $user.UserPrincipalName "| Last Login Date -" $lastLoginDate.CreationDate
logtoText $filePath ($user.UserPrincipalName + "," + $lastLoginDate.CreationDate)
Write-Output $user.UserPrincipalName
}
Clear-Content $filePath
logtoText $filePath ('Username', 'Last Login DateTime')
#Get All External Users
$allExternalUsers = Get-MsolUser -All | Where-Object -FilterScript { $_.UserPrincipalName -Like $externalUserExtention }
ForEach($externalUser in $allExternalUsers) {
find_last_login_date($externalUser)
}
The output is as expected:
User user1#external.com#EXT##tenant.onmicrosoft.com | Last Login Date -
user1#external.com#EXT##tenant.onmicrosoft.com
User user2#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 11/04/2020 12:02:12
user2#external.com#EXT##tenant.onmicrosoft.com
...
User userNNNN#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 12/04/2020 14:22:25
userNNNN#external.com#EXT##tenant.onmicrosoft.com
...
The extra Write-Output inside find_last_login_date is just their for debugging this.
Now when I change it to ForEach-Object it does something weird. Probably it makes sense to the experts, but not to me :-)
Replace
ForEach($externalUser in $allExternalUsers) {
find_last_login_date($externalUser)
}
With
ForEach-Object -InputObject $allExternalUsers {
find_last_login_date($_)
}
And the output becomes
User userX#external.com#EXT##tenant.onmicrosoft.com | Last Login Date - 10/01/2021 14:05:36 05/01/2021 20:48:45 05/01/2021 16:09:25 04/01/2021 07:36:26 22/12/2020 08:01:07 19/12/2020 10:24:08 18/12/2020 14:20:51 18/12/2020 08:05:55 16/12/2020 17:32:28 14/12/2
020 08:20:56 13/12/2020 07:58:13 11/12/2020 10:58:58 10/12/2020 08:08:28
user1#external.com#EXT##tenant.onmicrosoft.com
user2#external.com#EXT##tenant.onmicrosoft.com
...
userNNNN#external.com#EXT##tenant.onmicrosoft.com
...
So it seems to trigger the script to only look for the last login date once and also the select top 1 fails, etc.
Any idea what I am doing wrong? At first I thought it was about the "nested" ForEach-Object but even with the code to do the search inside a function it keeps doing this.
Is it by default doing things in parallel and do I need to wait for completion of all tasks? Something telse?
I'm looking at ForEach-Object to parallelize the above script as it takes a very long time to run on a large tenant.
From the docs :
When you use the InputObject parameter with ForEach-Object, instead of piping command results to ForEach-Object, the InputObject value is treated as a single object. This is true even if the value is a collection that is the result of a command, such as -InputObject (Get-Process). Because InputObject cannot return individual properties from an array or collection of objects, we recommend that if you use ForEach-Object to perform operations on a collection of objects for those objects that have specific values in defined properties, you use ForEach-Object in the pipeline.
Change
ForEach-Object -InputObject $allExternalUsers {
find_last_login_date($_)
}
into
$allExternalUsers | ForEach-Object {
find_last_login_date($_)
}
I currently have a script that retrieves the last modified date of the .vmx in a VM's datastore in vCenter. I need to make changes to instead use and display the last date in the vmware.log file (located in the same datastore as the .vmx)
I'm not sure how to grab that line and convert it to a XX/XX/XXXX format. In the log file, it shows it as Dec 23 10 for example. If this is not possible, no worries. I just need to pull the last line in the log file and export it to a .csv file. Below is my current code:
add-pssnapin VMware.VimAutomation.Core
# ---------- Only modify the fields in this area -------------
$vCenter = 'qlab-copsmgr' #name of the vCenter
$dataCenter = 'Fly-away Kit' #name of the DataCenter
$outputFile = $vCenter + '-LastDateUsed.csv' #desired output file name
# ---------- No modification is needed in the below code. Do not edit -------------
$columnName = "Name,DataStore,Date Last Used" | Out-File .\$OutputFile -Encoding ascii
Connect-VIServer $vCenter -WarningAction SilentlyContinue
$vmList = Get-VM | where { $_.PowerState -eq “PoweredOff”} | select Name
$vmList = $vmList -replace 'Name : ', '' -replace '#{Name=', '' -replace '}', ''
ForEach ($VM in $vmList)
{
# Get configuration and path to vmx file
$VMconfig = Get-VM $VM | Get-View | select config
$VMXpath = $VMconfig.config.files.VMpathName
# Remove and/or replace unwanted strings
$VMXpath = $VMXpath -replace '\[','' -replace '\] ','\' -replace '#{Filename=','/' -replace '}','' -replace '/','\'
# List the vmx file in the datastore
$VMXinfo = ls vmstores:\$VCenter#443\$DataCenter\$VMXpath | Where {$_.LastWriteTime} | select -first 1 | select FolderPath, LastWriteTime
# Remove and/or replace unwanted strings
$VMXinfo = $VMXinfo -replace 'DatastoreFullPath=', '' -replace '#{', '' -replace '}', '' -replace ';', ',' -replace 'LastWriteTime=', ''
# Output vmx information to .csv file
$output = $VM + ', ' + $VMXinfo
$output
echo $output >> $OutputFile
}
I also needed to pull the last event from the vmware.log file in order to backtrack the power off time for VMs where there is no vCenter event history. I looked at file timestamps but found that some VM processes and possibly backup solutions can make them useless.
I tried reading the file in place but ran into issues with the PSDrive type not supporting Get-Content in place. So for better or worse for my solution I started with one of LucD's scripts - the 'Retrieve the logs' script from http://www.lucd.info/2011/02/27/virtual-machine-logging/ which pulls a VMs vmware.log file and copies it to local storage. I then modified it to copy the vmware.log file to a local temp folder, read the last line from the file before deleting the file and return the last line of the log as a PS object.
Note, this is slow and I'm sure my hacks to LucD's script are not elegant, but it does work and I hope if helps someone.
Note: This converts the time value from the log to a PS date object by simple piping the string timestamp from the file into Get-Date. I've read that this does not work as expected for non-US date formatting. For those outside of the US you might want to look into this or just pass the raw timestamp string from the log instead of converting it.
#Examples:
#$lastEventTime = (Get-VM -Name "SomeVM" | Get-VMLogLastEvent).EventTime
#$lastEventTime = Get-VMLogLastEvent -VM "SomeVM" -Path "C:\alternatetemp\"
function Get-VMLogLastEvent{
param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)][PSObject[]]$VM,
[string]$Path=$env:TEMP
)
process{
$report = #()
foreach($obj in $VM){
if($obj.GetType().Name -eq "string"){
$obj = Get-VM -Name $obj
}
$logpath = ($obj.ExtensionData.LayoutEx.File | ?{$_.Name -like "*/vmware.log"}).Name
$dsName = $logPath.Split(']')[0].Trim('[')
$vmPath = $logPath.Split(']')[1].Trim(' ')
$ds = Get-Datastore -Name $dsName
$drvName = "MyDS" + (Get-Random)
$localLog = $Path + "\" + $obj.Name + ".vmware.log"
New-PSDrive -Location $ds -Name $drvName -PSProvider VimDatastore -Root '\' | Out-Null
Copy-DatastoreItem -Item ($drvName + ":" + $vmPath) -Destination $localLog -Force:$true
Remove-PSDrive -Name $drvName -Confirm:$false
$lastEvent = Get-Content -Path $localLog -Tail 1
Remove-Item -Path $localLog -Confirm:$false
$row = "" | Select VM, EventType, Event, EventTime
$row.VM = $obj.Name
($row.EventTime, $row.EventType, $row.Event) = $lastEvent.Split("|")
$row.EventTime = $row.EventTime | Get-Date
$report += $row
}
$report
}
}
That should cover your request, but to expound further on why I needed the detail, which reading between the lines may also benefit you, I'll continue.
I inherited hundreds of legacy VMs that have been powered off from various past acquisitions and divestitures and many of which have been moved between vCenter instances losing all event log detail. When I started my cleanup effort in just one datacenter I had over 60TB of powered off VMs. With the legacy nature of these there was also no detail available on who owned or had any knowledge of these old VMs.
For this I hacked another script I found, also from LucD here: https://communities.vmware.com/thread/540397.
This will take in all the powered off VMs, attempt to determine the time powered off via vCenter event history. I modified it to fall back to the above Get-VMLogLastEvent function to get the final poweroff time of the VM if event log detail is not available.
Error catching could be improved - this will error on VMs where for one reason or another there is no vmware.log file. But quick and dirty I've found this to work and provides the detail on what I need for over 90%.
Again this relies on the above function and for me at least the errors just fail through passing through null values. One could probably remove the errors by adding a check for vmware.log existance before attempting to copy it though this would add a touch more latency in execution due to the slow PSDrive interface to datastores.
$Report = #()
$VMs = Get-VM | Where {$_.PowerState -eq "PoweredOff"}
$Datastores = Get-Datastore | Select Name, Id
$PowerOffEvents = Get-VIEvent -Entity $VMs -MaxSamples ([int]::MaxValue) | where {$_ -is [VMware.Vim.VmPoweredOffEvent]} | Group-Object -Property {$_.Vm.Name}
foreach ($VM in $VMs) {
$lastPO = ($PowerOffEvents | Where { $_.Group[0].Vm.Vm -eq $VM.Id }).Group | Sort-Object -Property CreatedTime -Descending | Select -First 1
$lastLogTime = "";
# If no event log detail, revert to vmware.log last entry which takes more time...
if (($lastPO.PoweredOffTime -eq "") -or ($lastPO.PoweredOffTime -eq $null)){
$lastLogTime = (Get-VMLogLastEvent -VM $VM).EventTime
}
$row = "" | select VMName,Powerstate,OS,Host,Cluster,Datastore,NumCPU,MemMb,DiskGb,PoweredOffTime,PoweredOffBy,LastLogTime
$row.VMName = $vm.Name
$row.Powerstate = $vm.Powerstate
$row.OS = $vm.Guest.OSFullName
$row.Host = $vm.VMHost.name
$row.Cluster = $vm.VMHost.Parent.Name
$row.Datastore = $Datastores | Where{$_.Id -eq ($vm.DatastoreIdList | select -First 1)} | Select -ExpandProperty Name
$row.NumCPU = $vm.NumCPU
$row.MemMb = $vm.MemoryMB
$row.DiskGb = Get-HardDisk -VM $vm | Measure-Object -Property CapacityGB -Sum | select -ExpandProperty Sum
$row.PoweredOffTime = $lastPO.CreatedTime
$row.PoweredOffBy = $lastPO.UserName
$row.LastLogTime = $lastLogTime
$report += $row
}
# Output to screen
$report | Sort Cluster, Host, VMName | Select VMName, Cluster, Host, NumCPU, MemMb, #{N='DiskGb';E={[math]::Round($_.DiskGb,2)}}, PoweredOffTime, PoweredOffBy | ft -a
# Output to CSV - change path/filename as appropriate
$report | Sort Cluster, Host, VMName | Export-Csv -Path "output\Powered_Off_VMs_Report.csv" -NoTypeInformation -UseCulture
Cheers!
I pray this pays back some of the karma I've used.
Meyeaard
I have made a script that checks line by line and if string is found changes it to desired format
#example input you can use get-content PATH to txt or any file and assign it to $lines variable
$lines = #"
ernfoewnfnsf
ernfoewnfnsf
Dec 23 10 sgdsgdfgsdadasd
"# -split "\r\n"
#checks line by line and if find anything that maches start of the line, one Big letter two small, space, two digits, space, two digits, space
$lines | ForEach-Object{
if ($_ -match "^[A-Z][a-z]{2}\s\d{2}\s\d{2}\s")
{
$match = [convert]::ToDateTime($matches[0])
$_ -replace $matches[0], "$($match.ToShortDateString()) " | out-file { PATH } -APPEND
}
else
{
$_ | out-file { PATH } -APPEND
}
}
just change {PATH} with a filenamePAth and this should work for you
I am writing a script for work and trying to determine why my code is showing errors. I am new to this coding and want to understand what is wrong.
The errors I get are from the tag .... PC listings in my .txt file.
Ex: Get-Content : Cannot find path 'F:\tag 77909' because it does not exist.
My confusion is that when I write-host after the .Replace code it prints correctly
Ex:You cannot call a method on a null-valued expression. + $Notags =$PC.Replace <<<< ("tag ", "PC")
+ CategoryInfo : InvalidOperation: (Replace:String) [], RuntimeEx
ception
+ FullyQualifiedErrorId : InvokeMethodOnNull
Last error I get is that it only prints out the last PC.... ID in my .txt file listing??? I am unsure why given I have a foreach loop
**MY CODE SO FAR:**
Import-Module activedirectory
$compImports = Get-Content "C:\Temp\temp\input.txt"
$groupExport = "C:\temp\temp\output.txt"
Clear-Content $groupExport
$Header = "PC Name" + "|" + "Group Name" + "|" + "Group Description"
#Write header
$Header | Out-File $groupExport -Append
#get PC tag listing
$PCs = Get-Content $compImports
#For loop to change all "tag " to "PC"
foreach($PC in $PCS)
{
$Notags =$PC.Replace("tag ", "PC")
}
#loop to get information and print it out
foreach ($Notag in $Notags) {
$computerobj = Get-ADComputer $Notag -Properties memberof
$computerobj.memberof | ? {$_ -match '^CN=APP.*'} `
| % {get-adgroup $_ -Properties name, description} | `
% {$computerobj.Name + "|" + $_.name + "|" + $_.description `
| Out-File $groupExport -Append}
}
I see at least one issue here
$compImports = Get-Content "C:\Temp\temp\input.txt"
...
$PCs = Get-Content $compImports
You are calling Get-Content twice which would generate the error you are seeing most likely.
Could be simplified as
$PCs = Get-Content "C:\Temp\temp\input.txt"
Your other error should go away as a result since $PCs should contain real data at that point.
I have a series of documents that are going through the following function designed to count word occurrences in each document. This function works fine outputting to the console, but now I want to generate a text file containting the information, but with the file name appended to each word in the list.
My current console output is:
"processing document1 with x unique words occuring as follows"
"word1 12"
"word2 8"
"word3 3"
"word4 4"
"word5 1"
I want a delimited file in this format:
document1;word1;12
document1;word2;8
document1;word3;3
document1;word4;4
document1;word1;1
document2;word1;16
document2;word2;11
document2;word3;9
document2;word4;9
document2;word1;13
While the function below gets me the lists of words and occurences, I'm having a hard time figuring out where or how to insert the filename variable so that it prints at the head of each line. MSDN has been less-than helpful, and most of the places I try to insert the variable result in errors (see below)
function Count-Words ($docs) {
$document = get-content $docs
$document = [string]::join(" ", $document)
$words = $document.split(" `t",[stringsplitoptions]::RemoveEmptyEntries)
$uniq = $words | sort -uniq
$words | % {$wordhash=#{}} {$wordhash[$_] += 1}
Write-Host $docs "contains" $wordhash.psbase.keys.count "unique words distributed as follows."
$frequency = $wordhash.psbase.keys | sort {$wordhash[$_]}
-1..-25 | %{ $frequency[$_]+" "+$wordhash[$frequency[$_]]} | Out-File c:\out-file-test.txt -append
$grouped = $words | group | sort count
Do I need to create a string to pass to the out-file cmdlet? is this just something I've been putting in the wrong place on the last few tries? I'd like to understand WHY it's going in a particular place as well. Right now I'm just guessing, because I know I have no idea where to put the out-file to achieve my selected results.
I've tried formatting my command per powershell help, using -$docs and -FilePath, but each time I add anything to the out-file above that runs successfully, I get the following error:
Out-File : Cannot validate argument on parameter 'Encoding'. The argument "c:\out-file-test.txt" does not bel
ong to the set "unicode,utf7,utf8,utf32,ascii,bigendianunicode,default,oem" specified by the ValidateSet attribute. Sup
ply an argument that is in the set and then try the command again.
At C:\c.ps1:39 char:71
+ -1..-25 | %{ $frequency[$_]+" "+$wordhash[$frequency[$_]]} | Out-File <<<< -$docs -width 1024 c:\users\x46332\co
unt-test.txt -append
+ CategoryInfo : InvalidData: (:) [Out-File], ParameterBindingValidationException
+ FullyQualifiedErrorId : ParameterArgumentValidationError,Microsoft.PowerShell.Commands.OutFileCommand
I rewrote most of your code. You should utilize objects to make it easier formatting the way you want. This one splits on "space" and groups words together. Try this:
Function Count-Words ($paths) {
$output = #()
foreach ($path in $paths) {
$file = Get-ChildItem $path
((Get-Content $file) -join " ").Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries) | Group-Object | Select-Object -Property #{n="FileName";e={$file.BaseName}}, Name, Count | % {
$output += "$($_.FileName);$($_.Name);$($_.Count)"
}
}
$output | Out-File test-out2.txt -Append
}
$filepaths = ".\test.txt", ".\test2.txt"
Count-Words -paths $filepaths
It outputs like you asked(document;word;count). If you want documentname to include extension, change $file.BaseName to $file.Name . Testoutput:
test;11;1
test;9;2
test;13;1
test2;word11;5
test2;word1;4
test2;12;1
test2;word2;2
Slightly different approach:
function Get-WordCounts ($doc)
{
$text_ = [IO.File]::ReadAllText($doc.fullname)
$WordHash = #{}
$text_ -split '\b' -match '\w+'|
foreach {$WordHash[$_]++}
$WordHash.GetEnumerator() |
foreach {
New-Object PSObject -Property #{
Word = $_.Key
Count = $_.Value
}
}
}
$docs = gci c:\testfiles\*.txt |
sort name
&{
foreach ($doc in dir $docs)
{
Get-WordCounts $doc |
sort Count -Descending |
foreach {
(&{$doc.Name;$_.Word;$_.Count}) -join ';'
}
}
} | out-file c:\somedir\wordcounts.txt
Try this:
$docs = #("document1", "document2", ...)
$docs | % {
$doc = $_
Get-Content $doc `
| % { $_.split(" `t",[stringsplitoptions]::RemoveEmptyEntries) } `
| Group-Object `
| select #{n="Document";e={$doc}}, Name, Count
} | Export-CSV output.csv -Delimiter ";" -NoTypeInfo
If you want to make this into a function you could do it like this:
function Count-Words($docs) {
foreach ($doc in $docs) {
Get-Content $doc `
| % { $_.split(" `t",[stringsplitoptions]::RemoveEmptyEntries) } `
| Group-Object `
| select #{n="Document";e={$doc}}, Name, Count
}
}
$files = #("document1", "document2", ...)
Count-Words $files | Export-CSV output.csv -Delimiter ";" -NoTypeInfo
I got this Powershell script that queries users that have not changed their password for 24 hours. The query redirects the output to csv file. Below are the Powershell script and batch script:
Powershell script:
$root = [ADSI]''
$searcher = new-object System.DirectoryServices.DirectorySearcher($root)
$searcher.filter = "(&(objectCategory=person)(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))"
$searcher.sizelimit = 5000
[Void]$searcher.PropertiesToLoad.Add("cn")
[Void]$searcher.PropertiesToLoad.Add("samAccountName")
[Void]$searcher.PropertiesToLoad.Add("pwdLastSet")
$users = $searcher.findall()
$UserOU = "OU=Mountain,DC=Atlanta,DC=ga"
$PWDays = (Get-Date).AddDays(-1)
$UserCount = 0
$UserPW = 0
foreach($user in $users)
{
if ($user.path -like "*$UserOU")
{
$usercount = $UserCount
if ([datetime]::FromFileTime(($user.properties.pwdlastset)[0]) -le $PWDays)
{
$UserPW = $UserPW + 1
Write-Host $user.Properties.cn
}
}
}
Batch script:
powershell.exe d:\temp\query.ps1 > D:\temp\query.csv
My question is: How do I put change the script to put header for username in the the csv output file?
The header may simple be 'Username' not necessarily Firstname and Lastname.
Any reason why you aren't using Export-Csv? You can just pipe your objects into it and it will include headers. Something along the lines of
$users |
? { $_.Path -like "*$UserOU" } |
? { [datetime]::FromFileTime(($user.properties.pwdlastset)[0]) -le $PWDays } |
% { $_ | Add-Member -PassThru NoteProperty Username $_.Properties.cn } |
select Username |
Export-Csv D:\temp\query.csv
might work. (Hint: The pipeline is more fun than the loop :))
Not sure (never have user PS) but I guess that sticking
Write-Host "Username"
before the foreach, might do the trick