PowerShell Script Performance Optimization - performance

I am running a PowerShell script that gets some information from a csv file, stores it in an object array and then does some action depending on what's on the file. It actually only does one thing:
If one column has a AD group it copies the row for every member of that group.
The thing is I am really new at scripting and at the beginning the files were small, so everything went ok. Now I have huge files and the script is taking hours to execute.
$file = "c:\Report.csv"
$fileContent = Import-csv $file | select *, UserName
foreach($item in $fileContent)
{
$LoginName = $item.LoginName
$LoginNameClean = $LoginName.split("\")
$LoginNameClean = $LoginNameClean[1].trimstart("_")
$ObjectClass = (Get-ADObject -filter {SamAccountName -eq $LoginNameClean}).ObjectClass
$UserName = $null
if($ObjectClass -eq "user")
{
$UserName = Get-ADUser -identity $LoginNameClean -properties DisplayName
if($UserName)
{
$item.UserName = $UserName.DisplayName
}
}
elseif($ObjectClass -eq "group")
{
$GroupUsers = Get-ADGroupMember -identity $LoginNameClean -Recursive
foreach($user in $GroupUsers)
{
$UserInsideGroup = Get-ADUser -identity $user -properties DisplayName
$UserInsideGroupName = $UserInsideGroup.DisplayName
$newRow = New-Object PsObject -Property #{"URL" = $item.URL; "SiteListFolderItem" = $item.SiteListFolderItem; "TitleName" = $item.TitleName; "PermissionType" = $item.PermissionType; "LoginName" = $item.LoginName; "Permissions" = $Item.Permissions; "UserName" = $UserInsideGroup.DisplayName;}
$fileContent += $newRow
}
}
}
$fileContent | Export-Csv -NoTypeInformation -Path "c:\ReportUpgraded.csv"
Any tips on how to improve the performance of this is much appreciated
Thanks in advance.
edit: I am using PS 2.0
As commentaries suggested, I am trying to replace the fileContent += newRow.
I am trying to use add member but it's giving me this error:
Add-Member : Cannot add a member with the name "URL" because a member
with that name already exists. If you wan t to overwrite the member
anyway, use the Force parameter to overwrite it. At line:1 char:26
+ $fileContent | Add-Member <<<< -MemberType NoteProperty -Name "URL"-Value "teste"
+ CategoryInfo : InvalidOperation: (#{SiteListFolde...me=; URL=teste}:PSObject) [Add-Member], Inv
alidOperationException
+ FullyQualifiedErrorId : MemberAlreadyExists,Microsoft.PowerShell.Commands.AddMemberCommand
How I can I use this properly? Add-member is not adding but replacing members

I manage to reduce 30 times the execution time with a couple of things.
First, I switched array to a array list so that I could use theArray.Add() method. Then, in order to stop making requests to the AD all the time, I am saving the information in excel sheets with the name of the group, so that it will only request AD once per group.
Here is the script:
$file = "ReportBefore.csv"
$fileContent = Import-csv $file | select *, UserName
[System.Collections.ArrayList]$ArrayList = $fileContent
foreach($item in $fileContent)
{
$LoginName = $item.LoginName
$LoginNameClean = $LoginName.split("\")
$LoginNameClean = $LoginNameClean[1].trimstart("_")
$ObjectClass = (Get-ADObject -filter {SamAccountName -eq $LoginNameClean}).ObjectClass
$UserName = $null
if($ObjectClass -eq "user")
{
$UserName = Get-ADUser -identity $LoginNameClean -properties DisplayName
if($UserName)
{
$item.UserName = $UserName.DisplayName
}
}
elseif($ObjectClass -eq "group")
{
$exportString = "\\folder\username$\Desktop\ADGroups\" + $LoginNameClean + ".csv"
if([System.IO.File]::Exists($exportString))
{
$GroupUsers = Import-csv $exportString | select *
}
else
{
$GroupUsers = Get-ADGroupMember -identity $LoginNameClean -Recursive | Select samAccountName,Name, #{Name="DisplayName";Expression={(Get-ADUser $_.distinguishedName -Properties Displayname).Displayname}}
$GroupUsers | Export-Csv -NoTypeInformation -Path $exportString
}
foreach($user in $GroupUsers)
{
$UserInsideGroupName = $user.DisplayName
$newRow = New-Object PsObject -Property #{"URL" = $item.URL; "SiteListFolderItem" = $item.SiteListFolderItem; "TitleName" = $item.TitleName; "PermissionType" = $item.PermissionType; "LoginName" = $item.LoginName; "Permissions" = $Item.Permissions; "UserName" = $UserInsideGroupName;}
#$filecontent += $newRow
$ArrayList.Add($newRow)
}
}
}
$ArrayList | Export-Csv -NoTypeInformation -Path "\ReportAfter.csv"

Related

System.Collections.Generic.List`1[Amazon.EC2.Model.GroupIdentifier]

trying export security groups but getting this in csv
System.Collections.Generic.List`1[Amazon.EC2.Model.GroupIdentifier]
any idea why? I think its because it cant export and array. but how do I convert this line to string
$ec2.SecurityGroups
I tried $ec2.SecurityGroups -join ", " and it wont do it
$results = #()
$ec2instances = get-ec2instance
$ec2list = $ec2instances.Instances
foreach ($ec2 in $ec2list) {
$result = "" | select Name, InstanceId, Platform, AMI, InstanceType, PrivateIpAddress, SecurityGroups, AvailabilityZone
$result.Name = ($ec2 | select-object -expandProperty tags | where-object -property Key -eq Name).value
$result.InstanceId = $ec2.InstanceId
$result.Platform = $ec2.Platform
$result.AMI = $ec2.ImageId
$result.InstanceType = $ec2.InstanceType
$result.PrivateIpAddress = $ec2.PrivateIpAddress
$result.SecurityGroups = $ec2.SecurityGroups
$result.AvailabilityZone = $ec2.placement.AvailabilityZone
$results += $result
}
#$results | ft -autosize
$results | export-csv -NoTypeinformation -USeculture aws_ec2_report.csv -Force
ok figured this out with
$result.SecurityGroups = ($ec2.SecurityGroups.groupname -join " | ")

How do I generate this report by importing the hostname to a list or csv?

I had this script, but the format was in HTML and I cleaned up all the code and changed the commands that were "gwmi" to "Get-CimInstance" to have good practices. My goal is to transform this script that handles a hostname to a list of hostnames.
Can someone help me?
The idea I had would be to have a window that has a "browse" button to import the list, be it in txt or csv and for each hostname in that list it would do these commands and in the end it would export to an xlsx file (I tried with Export-Excel, but you need to download the module separately, and you need to trust the repository, authorize the import of the module for later use, so I would have to make this standalone, without any request, because I would convert this ps1 into an exe file) and the data, would need to be side by side with the headers, e.g. Hostname, Last User Logged, Type Of Chassis etc.
I would be very grateful if someone can help me, I've been building part 1 of this script for a few hours now, and now I need to go to part 2 (that is this process to create and export csv results) which is the part where I feel stuck.
Add-Type -AssemblyName Microsoft.VisualBasic
$ComputerName = [Microsoft.VisualBasic.Interaction]::InputBox("Insert the hostname Name", "Hardware Report")
If ($ComputerName -eq "")
{
break
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
$ProgressPreference = 'SilentlyContinue'
try {
$ErrorActionPreference = "Stop";
$TestComputerHost = Test-Connection $ComputerName -Count 1 -InformationAction Continue -WarningAction SilentlyContinue;
} catch {
#Hostname will be skipped
} finally {
$ProgressPreference = 'Continue'
$ErrorActionPreference = "Continue"
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$ErrorActionPreference = "Stop"
Get-CimInstance win32_operatingsystem -ComputerName $ComputerName | Out-Null }
catch [System.Runtime.InteropServices.COMException]
{
#Hostname will be skipped
}
#Validate if the WS Management service is enabled on the remote device
$ProgressPreference = 'SilentlyContinue'
$TestComputerHost = Test-NetConnection $ComputerName -Port 5985 -InformationLevel Quiet -WarningAction SilentlyContinue
If ($TestComputerHost -ne "False"){
}
$ProgressPreference = 'Continue'
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param (
)
Set-StrictMode -Version 'Latest'
[int[]]$chassisType = try {
$ErrorActionPreference = "Stop";
Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -ExpandProperty ChassisTypes;
} catch {
#Here need to be blank result or skip this result but keep the rest results
} finally {
$ErrorActionPreference = "Continue";
}
switch ($chassisType) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser {
try {
$ErrorActionPreference = "Stop"
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "DOMAIN") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]
{
}
}
#Name of remote computer
$Name = 'Hostname' + $ComputerName
#Get last logged user (by function)
$LastLoggedUser = ((Get-LastUser).Split('\')[1])
#Last Boot Time
$LastBoot = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName |Select-Object CSName, LastBootUpTime | Select -ExpandProperty LastBootUpTime).tostring("dd/MM/yyyy hh:mm:ss")
#Chassis Type Of Computer
$ChassisType = Get-WUChassisType
#Operating System
$OS = (Get-CimInstance win32_operatingsystem -ComputerName $ComputerName).caption
#System Installed On
$SystemInstalledOn = ((Get-CimInstance Win32_OperatingSystem -ComputerName $ComputerName).InstallDate).tostring("dd/MM/yyyy hh:mm:ss")
#Processor
$Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size
#Ram Memory
$Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity
#Serial Number
$SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
#Export to CSV
$Name + $LastLoggedUser + $ChassisType + $LastBoot + $OS + $SystemInstalledOn + $Processor + $Disk + $Ram + $SerialNumber + $Manufacturer + $Model | Export-Csv
#Dialog box to information finish script
[Microsoft.VisualBasic.Interaction]::MsgBox("Report is finished", "OKOnly,SystemModal,Information", "Success") | Out-Null```
edit: rewrote it a little for you, try the below.
Note that the input csv expects a header called "ComputerName" and a list of computer names underneath that.
I copied the csv-to-excel part at the bottom from here
#Function to let user select a file then return the filepath.
Function Get-FileName($initialDirectory){
[System.Reflection.Assembly]::LoadWithPartialName(“System.windows.forms”) | Out-Null
$OpenFileDialog = New-Object System.Windows.Forms.OpenFileDialog
$OpenFileDialog.initialDirectory = $initialDirectory
$OpenFileDialog.filter = “CSV Exports (*.csv)| *.csv”
$OpenFileDialog.ShowDialog() | Out-Null
$OpenFileDialog.FileName
}
$filepath = Get-FileName -initialDirectory $PSScriptRoot
$csvdata = Import-Csv -Path $filepath
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
Function Get-WUChassisType {
[CmdletBinding()]
param ($ComputerName=$null)
Set-StrictMode -Version 'Latest'
$chassisTypes = Get-CimInstance Win32_SystemEnclosure -ComputerName $ComputerName | Select -ExpandProperty ChassisTypes
switch ($chassisTypes) {
{ $_ -in 3, 4, 5, 6, 7, 15, 16 } {
return 'Desktop'
}
{ $_ -in 8, 9, 10, 11, 12, 14, 18, 21, 31, 32 } {
return 'Notebook'
}
{ $_ -in 30 } {
return 'Tablet'
}
{ $_ -in 17, 23 } {
return 'Servidor'
}
Default {
return ''
}
}
}
#Function to get last logged user on remote computer
Function Get-LastUser($ComputerName){
try {
Get-WmiObject Win32_LoggedOnUser -ComputerName $ComputerName |
Select Antecedent -Unique |
% {
$domain = $_.Antecedent.Split('"')[1]
if($domain -eq "VLINET") {
"{0}\{1}" -f $domain, $_.Antecedent.Split('"')[3]
}
} | Select-Object -First 1
} catch [System.Runtime.InteropServices.COMException]{
}
}
#We'll append all our individual pc resuls into this array
$exportObj = #()
$offlineObj = #()
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
foreach ($row in $csvdata){
if(Test-Connection $row.ComputerName -Count 1){
$ciminfo = Get-CimInstance win32_operatingsystem -ComputerName $row.ComputerName -ErrorAction SilentlyContinue |Select-Object CSName, LastBootUpTime, Caption, InstallDate
$sysinfo = Get-CimInstance win32_ComputerSystemProduct -ComputerName $row.ComputerName -ErrorAction SilentlyContinue | Select-Object Vendor, Name
if ($ciminfo -and $sysinfo){
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
ChassisType = Get-WUChassisType -ComputerName $row.ComputerName;
LastLoggedUser = ((Get-LastUser -ComputerName $row.ComputerName).Split('\')[1]);
LastBoot = $ciminfo.LastBootUpTime.tostring("dd/MM/yyyy hh:mm:ss");
OS = $ciminfo.Caption;
SystemInstalledOn = $ciminfo.InstallDate.tostring("dd/MM/yyyy hh:mm:ss");
Processor = (Get-CimInstance win32_processor -ComputerName $row.ComputerName -filter "deviceid='CPU0'").Name;
Disk = (Get-CimInstance Win32_LogicalDisk -ComputerName $row.ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size;
Ram = (Get-CimInstance Win32_PhysicalMemory -ComputerName $row.ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity;
SerialNumber = Get-CimInstance win32_bios -ComputerName $row.ComputerName | Select-Object -ExpandProperty SerialNumber;
Manufacturer = $sysinfo.Vendor;
Model = $sysinfo.Name;
}
#Add each PC results as a new row in our array
$exportObj += $objPcResult
}
}else{
$objPcResult = New-Object PSObject -Property #{
Name = 'Hostname: ' + $row.ComputerName;
}
$offlineObj += $objPcResult
}
}
#Setup our temp variables to save our collected data as a temporary CSV, so we can import it into Excel to save as an XLSX.
$offlinecsv = "c:\temp\offline.csv" #Location of offline hosts
$tempcsv = "c:\temp\temp.csv" #Location of the source file
$xlsx = "c:\temp\output.xlsx" #Desired location of output
$delimiter = "," #Specify the delimiter used in the file
#Temp export our csv - to be converted to xlsx
$exportObj | Export-Csv -Path $tempcsv -NoTypeInformation
$offlineObj | Export-Csv -Path $offlinecsv -NoTypeInformation
### Create a new Excel Workbook with one empty sheet
$excel = New-Object -ComObject excel.application
$excel.Visible = $false
$workbook = $excel.Workbooks.Add(1)
$worksheet = $workbook.worksheets.Item(1)
# Build the QueryTables.Add command and reformat the data
$TxtConnector = ("TEXT;" + $tempcsv)
$Connector = $worksheet.QueryTables.add($TxtConnector,$worksheet.Range("A1"))
$query = $worksheet.QueryTables.item($Connector.name)
$query.TextFileOtherDelimiter = $delimiter
$query.TextFileParseType = 1
$query.TextFileColumnDataTypes = ,1 * $worksheet.Cells.Columns.Count
$query.AdjustColumnWidth = 1
# Execute & delete the import query
$query.Refresh()
$query.Delete()
# Save & close the Workbook as XLSX.
$Workbook.SaveAs($xlsx,51)
$excel.Quit()
Here is an example of how I'd make an initial pass at changing your function with the primary changes being:
Multiple calls were being made to the same class, remotely. This is really inefficient. The calls were consolidated to store the result of the first call in a single variable and then reference the variable's properties for the additional information.
Your script is setting preferences, globally, a lot. All PowerShell cmdlets allow you to set the ErrorActionPreference inline, so setting it globally back and forth is unnecessary.
By prestaging an output object ($temp) and emitting it where the code quits processing, you can see the results of partial communications failures (e.g. pingable but WinRM off, etc.).
Your chassis function shouldn't have worked. Since chassisTypes returns an array, you can't use the -in operator to check if an array exists in an array. I used some code from another SO article that shows some pretty cool PowerShell on how to make this value report the chassis values accurately.
The CIM instance of Win32_LoggedOnUser returns the 'domain' and 'name' properties directly so you don't need to string-parse the values using splits and array references.
Examples of using it in the desired states you specified in the question are at the bottom (accepting multiple computer names and accepting them from the contents of a file (not with a popup, but I hope you'll see how not using GUIs will be more helpful)).
For the amount of independent remote calls you're making, if you identify that the code runs slower than you'd like, you might think about adapting it to use Invoke-Command. This would pass all of your code to the remote machine once, process it on the remote machine, and just return the output object. In my experience, this dramatically reduces the execution time of the script (e.g. I was able to pull information from thousands of servers spanning the globe in about 15 minutes using Invoke-Command, whereas individual remote calls took 12 hours or more).
Function Generate-ComputerHwReport {
param(
[Parameter(Mandatory=$true)] [string[]] [ValidateNotNullOrEmpty()] $ComputerNames ## This lines requires the passed in value to be an array of strings
)
## Modification -- Looping through the array to check all computers passed in
foreach ($ComputerName in $ComputerNames) {
$temp = [pscustomobject] #{
TestPing = $false
TestWinRM = $false
TestWSMan = $false
Hostname = $ComputerName
LastUser = ''
LastBootTime = ''
ChassisType = ''
OS = ''
InstallDate = ''
Processor = ''
Disk = ''
Ram = ''
SerialNumber = ''
Manufacturer = ''
Model = ''
ErrorLog = ''
}
#Check computer online in network, if is not online, the hostname will be skipped but necessary add in log entry which computers is offline
if (Test-Connection $ComputerName -Count 1 -Quiet) {
$temp.TestPing = $true
} else {
$temp
continue
}
#Validade crucial service that is crucial for get remote data, and if is not possible to get this information, the hostname will be skipped
try {
$Win32_OS = Get-CimInstance win32_operatingsystem -ComputerName $ComputerName -ErrorAction Stop
$temp.TestWinRM = $true
$temp.LastBootTime = $Win32_OS.LastBootUpTime.ToString("dd/MM/yyyy hh:mm:ss")
$temp.OS = $Win32_OS.Caption
$temp.InstallDate = $Win32_OS.InstallDate.ToString("dd/MM/yyyy hh:mm:ss")
} catch {
#Hostname will be skipped
$temp
continue
}
#Validate if the WS Management service is enabled on the remote device
if ((Test-NetConnection $ComputerName -Port 5985).TcpTestSucceeded) {
$temp.TestWSMan = $true
} else {
$temp
continue
}
#Function to create the Get-WUChassisType that is performed to find out if the Chassis of the equipment is Notebook or Desktop, and it is not configured to detect virtual machine
## https://stackoverflow.com/questions/55184682/powershell-getting-chassis-types-info
$ChassisTypes = #{
Name = 'ChassisType'
Expression = {
# property is an array, so process all values
$result = foreach($value in $_.ChassisTypes)
{
switch([int]$value)
{
1 {'Other'}
2 {'Unknown'}
3 {'Desktop'}
4 {'Low Profile Desktop'}
5 {'Pizza Box'}
6 {'Mini Tower'}
7 {'Tower'}
8 {'Portable'}
9 {'Laptop'}
10 {'Notebook'}
11 {'Hand Held'}
12 {'Docking Station'}
13 {'All in One'}
14 {'Sub Notebook'}
15 {'Space-Saving'}
16 {'Lunch Box'}
17 {'Main System Chassis'}
18 {'Expansion Chassis'}
19 {'SubChassis'}
20 {'Bus Expansion Chassis'}
21 {'Peripheral Chassis'}
22 {'Storage Chassis'}
23 {'Rack Mount Chassis'}
24 {'Sealed-Case PC'}
default {"$value"}
}
}
$result
}
}
$temp.ChassisType = (Get-CimInstance -ClassName Win32_SystemEnclosure -ComputerName $ComputerName | Select-Object -Property $ChassisTypes).ChassisType
#Function to get last logged user on remote computer
try {
$t = Get-CimInstance win32_loggedonuser -ComputerName $ComputerName -ErrorAction Stop | Select Antecedent -Unique
$temp.LastUser = "{0}\{1}" -f $t.Antecedent.Domain, $t.Antecedent.Name
} catch {
$temp.ErrorLog += $_
}
#Processor
$temp.Processor = (Get-CimInstance win32_processor -ComputerName $ComputerName -filter "deviceid='CPU0'").Name
#Disk
$temp.Disk = ((Get-CimInstance Win32_LogicalDisk -ComputerName $ComputerName | Select-Object #{Name="Size"; Expression={"$([math]::round($_.Size / 1GB,2))GB"}}).Size) -join ', '
#Ram Memory
$temp.Ram = ((Get-CimInstance Win32_PhysicalMemory -ComputerName $ComputerName | Select-Object #{Name="Capacity"; Expression={"$([math]::round($_.Capacity / 1GB,2))GB"}}).Capacity) -join ', '
#Serial Number
$temp.SerialNumber = Get-CimInstance win32_bios -ComputerName $ComputerName | Select-Object -ExpandProperty SerialNumber
#Manufacturer
$temp.Manufacturer = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Vendor
#Model
$temp.Model = Get-CimInstance win32_ComputerSystemProduct -ComputerName $ComputerName | Select-Object -ExpandProperty Name
$temp
}
}
## Output to console
Generate-ComputerHwReport -ComputerNames localhost, pc2
## Output to console reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt)
## Output to CSV reading in the computer names from a file
Generate-ComputerHwReport -ComputerNames (gc listofcomputernames.txt) | Export-Csv -NoTypeInformation ComputerHwReport.csv

Get-ADUser - output a blank line fo CSV file when no data is found

I've cobbled together (from various online sources) a script that can return data about AD users from a CSV file. It can clearly mark when a user has NOT been found, but perhaps just as important to me is a way of outputting a blank line to the CSV file when the input CSV file also has a blank. That would save a lot mucking around in Excel to make sure that all the blank lines correspond to each other (and subsequently where user data has been found). A 1-to-1 comparsion is the goal.
Here is what I have...
$ImportCSV = "C:\Users\x\Desktop\testCSV_in.csv"
$Names = Import-Csv -Path $ImportCSV
$Results = foreach ($Name in $Names) {
$filter = $Name.samAccountName
$User = Get-ADUser -Filter "SamAccountName -like '$filter'" -Properties Samaccountname, Givenname, Surname, EmailAddress, Name
#blank lines input CSV
if ( $User.SamAccountName -eq "" ) {
# please help
}
# found user
if ( $User ) {
$User |
Select-Object -Property Samaccountname, Givenname, Surname, EmailAddress, Name
}
# not found user
else {
[pscustomobject]#{
SamAccountName = 'MISSING ACCOUNT'
}
}
}
$Results | Export-Csv -Path "C:\Users\x\Desktop\testCSV_out.csv" -NoTypeInformation -Encoding UTF8
All the possible combinations that I can think of for $User.SamAccountName -eq "" just return back a CSV file that doesn't reflect the gaps that I purposely introduced (to mimic real use-cases).
I am sure, it's a just a line or two code that's needed. Thanks.
If you want a normalized export you need a normalized Object, meaning, all columns for your CSV must exist for all lines, even if they're $null. Try this code, see if it works:
$out = {
param($samAccountName)
[pscustomobject]#{
Samaccountname = $samAccountName
Givenname = $User.GivenName
Surname = $User.SurName
EmailAddress = $User.EmailAddress
Name = $User.Name
}
}
Import-Csv -Path 'C:\Users\x\Desktop\testCSV_in.csv' | ForEach-Object {
if([string]::IsNullOrWhiteSpace($_.samAccountName)) {
return & $out -samAccountName 'User Not Found on CSV'
}
$params = #{
Properties = 'Samaccountname', 'Givenname', 'Surname', 'EmailAddress', 'Name'
LDAPFilter = "(SamAccountName={0})" -f $_.samAccountName
}
$user = Get-ADUser #params
if(-not $user) {
return & $out -samAccountName 'User Not Found on AD'
}
& $out -samAccountName $user.samAccountName
} | Export-Csv -Path "C:\Users\x\Desktop\testCSV_out.csv" -NoTypeInformation -Encoding UTF8
Edit: Import-Csv, same as ConvertFrom-Csv will skip empty lines. At least one column has to be populated.
I found a way to get this same functionality and also add manager email and calculated fields as well:
Import-CSV C:\test\CSV_in.csv |
ForEach-Object {
Try{
$u = Get-ADUser $_.samaccountname -Properties * -ErrorAction Stop | Select GivenName, Surname, samaccountname, Title, Department, #{N='Manager';E={(Get-ADUser $_.Manager).Name}},#{N="ManagerEmail";E={(Get-ADUser -Property emailaddress $_.Manager).emailaddress}}
[PSCustomObject]#{User = $_.name; samaccountname = $u.samaccountname; Manager = $u.Manager; ManagerEmail = $u.ManagerEmail}
}
Catch{
[PSCustomObject]#{User = $_.name; samaccountname = 'Not Found'; Manager = 'N/A'; ManagerEmail = 'N/A'}
}
} | Export-CSV c:\test\CSV_out.csv -NoTypeInformation

Database query output to CSV

Below is my PowerShell code and it is working fine.
[Reflection.Assembly]::LoadFile("E:\oracle\product\11.2.0\ODP.NET\bin\2.x\Oracle.DataAccess.dll")
$constr = "User Id=system;Password=pass;Data Source=API"
$conn= New-Object Oracle.DataAccess.Client.OracleConnection($constr)
$conn.Open()
$sql="select * from dba_users"
$command = New-Object Oracle.DataAccess.Client.OracleCommand($sql,$conn)
$reader=$command.ExecuteReader()
while($reader.Read()){
$reader.GetString(0)
}
$conn.Close()
The problem is I want to export result to CSV. How to do this in PowerShell? Also, how can I show it in tabular format in PowerShell screen or output?
I have done something like this in the past I havent exported it as a csv but it should work.
$someArray = #()
#read all rows into a hash table
while ($reader.Read())
{
$row = #{}
for ($i = 0; $i -lt $reader.FieldCount; $i++)
{
$row[$reader.GetName($i)] = $reader.GetValue($i)
}
#convert hashtable into an array of PSObjects
$someArray += new-object psobject -property $row
}
$conn.Close()
$someArray | export-csv C:\temp\someFile.csv
Build custom objects from the fields of each record, then export the list of objects to a CSV:
$colNames = $reader.GetSchemaTable() | select -Expand ColumnName
$data = while ($reader.Read()) {
$obj = New-Object -Type PSCustomObject
for ($i = 0; $i -lt $colNames.Count; $i++) {
$obj | Add-Member -Type NoteProperty -Name $colNames[$i] -Value $reader.GetString($i)
}
$obj
}
$data | Export-Csv 'C:\path\to\output.csv' -NoType
Code adopted from here (about halfway down the page, but you may want to read the article in its entirety).
Pipe the data into Format-Table to get tabular output in the PowerShell console:
$data | Format-Table -AutoSize

Loop through all bindings configured in IIS with powershell

I'm looking for a way to go through all binding settings already configured in my IIS.
Im using this to work with the IIS in Powershell:
Import-Module WebAdministration
So far I was able to get the main required information i want:
$Websites = Get-ChildItem IIS:\Sites
My array $Websites is filled correctly and with the following command...
$Websites[2]
..I recieve this result:
Name ID State Physical Path Bindings
---- -- ----- ------------- --------------
WebPage3 5 D:\Web\Page3 http *:80:WebPage3
https *:443:WebPage3
Now here's the part I having a hard time with:
I want to check if the binding is correct. In order to do that I only need the binding. I tried:
foreach ($site in $Websites)
{
$site = $Websites[0]
$site | select-string "http"
}
Debugging that code shows me that $Site doesn't contain what I expected: "Microsoft.IIs.PowerShell.Framework.ConfigurationElement". I currently have no clue how to explicitly get to the binding information in order to to something like this (inside the foreach loop):
if ($site.name -eq "WebPage3" -and $site.Port -eq "80") {
#website is ok
}
else {
#remove all current binding
#add correct binding
}
Thank you for your help!
Solution:
Import-Module WebAdministration
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string]$IP = $BindingInfo.SubString($BindingInfo.IndexOf(" "),$BindingInfo.IndexOf(":")-$BindingInfo.IndexOf(" "))
[string]$Port = $BindingInfo.SubString($BindingInfo.IndexOf(":")+1,$BindingInfo.LastIndexOf(":")-$BindingInfo.IndexOf(":")-1)
Write-Host "Binding info for" $Site.name " - IP:"$IP", Port:"$Port
if ($Site.enabledProtocols -eq "http") {
#DO CHECKS HERE
}
elseif($site.enabledProtocols -eq "https") {
#DO CHECKS HERE
}
}
I don't know exactly what you are trying to do, but I will try. I see that you reference $Websites[2] which is webPage3.
You can do it like this:
$site = $websites | Where-object { $_.Name -eq 'WebPage3' }
Then when you look at $site.Bindings, you will realize that you need the Collection member:
$site.bindings.Collection
On my machine this returns this:
protocol bindingInformation
-------- ------------------
http *:80:
net.tcp 808:*
net.pipe *
net.msmq localhost
msmq.formatname localhost
https *:443:
And the test might then look like this:
$is80 = [bool]($site.bindings.Collection | ? { $_.bindingInformation -eq '*:80:' })
if ($is80) {
#website is ok
} else {
#remove all current binding
#add correct binding
}
I sent content of Collection to pipeline and filtere only objects where property bindingInformation is equal to desired value (change it). Then I cast it to [bool]. This will return $true if there is desired item, $false otherwise.
I found that if there were multiple bindings on a site then if I needed to script access to individual parts of the bindings otherwise I only got the first binding. To get them all I needed the script to be extended as below:
Import-Module WebAdministration
$Websites = Get-ChildItem IIS:\Sites
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string[]]$Bindings = $BindingInfo.Split(" ")
$i = 0
$header = ""
Do{
Write-Output ("Site :- " + $Site.name + " <" + $Site.id +">")
Write-Output ("Protocol:- " + $Bindings[($i)])
[string[]]$Bindings2 = $Bindings[($i+1)].Split(":")
Write-Output ("IP :- " + $Bindings2[0])
Write-Output ("Port :- " + $Bindings2[1])
Write-Output ("Header :- " + $Bindings2[2])
$i=$i+2
} while ($i -lt ($bindings.count))
}
I had something similar to the last answer, but this corrects to HTTPS sites and adds a bit more information that is useful.
Import-Module WebAdministration
$hostname = hostname
$Websites = Get-ChildItem IIS:\Sites
$date = (Get-Date).ToString('MMddyyyy')
foreach ($Site in $Websites) {
$Binding = $Site.bindings
[string]$BindingInfo = $Binding.Collection
[string[]]$Bindings = $BindingInfo.Split(" ")#[0]
$i = 0
$status = $site.state
$path = $site.PhysicalPath
$fullName = $site.name
$state = ($site.name -split "-")[0]
$Collection = ($site.name -split "-")[1]
$status = $site.State
$anon = get-WebConfigurationProperty -Filter /system.webServer/security/authentication/AnonymousAuthentication -Name Enabled -PSPath IIS:\sites -Location $site.name | select-object Value
$basic = get-WebConfigurationProperty -Filter /system.webServer/security/authentication/BasicAuthentication -Name Enabled -PSPath IIS:\ -location $site.name | select-object Value
Do{
if( $Bindings[($i)] -notlike "sslFlags=*"){
[string[]]$Bindings2 = $Bindings[($i+1)].Split(":")
$obj = New-Object PSObject
$obj | Add-Member Date $Date
$obj | Add-Member Host $hostname
$obj | Add-Member State $state
$obj | Add-Member Collection $Collection
$obj | Add-Member SiteName $Site.name
$obj | Add-Member SiteID $site.id
$obj | Add-member Path $site.physicalPath
$obj | Add-Member Protocol $Bindings[($i)]
$obj | Add-Member Port $Bindings2[1]
$obj | Add-Member Header $Bindings2[2]
$obj | Add-member AuthAnon $Anon.value
$obj | Add-member AuthBasic $basic.value
$obj | Add-member Status $status
$obj #take this out if you want to save to csv| export-csv "c:\temp\$date-$hostname.csv" -Append -notypeinformation
$i=$i+2
}
else{$i=$i+1}
} while ($i -lt ($bindings.count))
}

Resources