How to kill all RDP sessions using powershell script - windows

I am writing powershell script like below but unable to read a sessionids from the query result then kill all the active sessions.
$queryResults = quser /server:$server; Write-Output "users : $users";
foreach($queryResult in $queryResults){
Write-Output "session ID : $queryResult.sessionid";
}
Any suggestion how to read a session ids from the queryResult then logoff all the sessions.

You need to parse the output of quser (it's not a PS command so the output is a simple string) - I've done it this way so that you receive an object that's more useful:
$server = 'myserver'
# Get list of logged in users
$queryResults = quser /server:$server
# Parse the quser output and store results in a new object. Skip first line as that's just headers.
$UserList = $queryResults | Select-Object -Skip 1 -ErrorAction Stop | ForEach-Object {
# Split up the current line into its elements
$CurrentLine = $_.Trim() -Replace '\s+',' ' -Split '\s'
# Create a new object from all the line elements. If session is disconnected different fields will be selected
If ($CurrentLine[2] -eq 'Disc') {
$SearchResult = [pscustomobject]#{
UserName = $CurrentLine[0];
SessionName = $null;
Id = $CurrentLine[1];
State = $CurrentLine[2];
IdleTime = $CurrentLine[3];
LogonTime = $CurrentLine[4..($CurrentLine.GetUpperBound(0))] -join ' '
}
}
Else {
$SearchResult = [pscustomobject]#{
UserName = $CurrentLine[0];
SessionName = $CurrentLine[1];
Id = $CurrentLine[2];
State = $CurrentLine[3];
IdleTime = $CurrentLine[4];
LogonTime = $CurrentLine[5..($CurrentLine.GetUpperBound(0))] -join ' '
}
}
# Output the custom object (stores it in $UserList)
$SearchResult
}
# Display a list of users/states/Id
$UserList | Select-Object UserName, Id, State, IdleTime
# To logoff everyone, you can just loop through
#$UserList | ForEach-Object {
# logoff $_.Id
#}
# Or you could just logoff the disconnected users:
#$UserList | Where-Object {$_.State -eq 'Disc'} | ForEach-Object {
# logoff $_.Id
#}

Related

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

Export reg value to csv

i have 1 question:
i need verify 3 reg key on 20 pc and export result on csv file.
I used this string
Get-ItemProperty -Path hklm:"\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\" -Name "keyname" | Export-csv -path "csvpath"
and recive the all value for thi key but i don't need see the "PSPath, PSParentPath, PSChildName, PSDrive, PSProvider.
now i was thinking of making a script with variables to simplify it, but at this point i would like it to tell me even if the key was not found and the basic thing i can run it from the DC to all machines (about 20).
this could be a starting point
$key1 = name key 1
$key2 = name key 2
$key3 = name key 3
$hostname= hostname
$regkey= get-itemprperty -path ecc....
and now i'm seeing how you implement the verification loop and export everything to csv
thx
To verify the key existence, use Test-Path.
Computer names and Key names as arrays of strings.
No experience with remoting, I think you'll be using Invoke-Command, but this should give you an idea of looping and getting all non-PS properties:
Computer1
Computer2
Computer3
'# -split '\n'
$keyNames = #'
KeyName1
KeyName2
KeyName3
`# -split '\n'
ForEach ( $Comoputer in $Computers) {
ForEach ( $KeyName in $KeyNames ) {
If ( Test-Path $KeyName )
{
$AllProps = ($key = Get-Item $KeyName).Property
(Get-ItemProperty $key).PSobject.Properties | where name -in $AllProps | select Name , Value
<< Create output >>
}
Else
{
"$ComputerName: $KeyName not found."
}
}
} | Export-Csv "\\Path\to\CsvFile"
To probe multiple computers for 3 registry properties and output the result in a CSV file, you can use Invoke-Command like below:
$computers = 'pc01','pc02','pc03' # etc. the 20 computers you want to probe
$propertynames = 'property1','property2','property3' # you may use wildcards here
# loop over the computers
$result = foreach ($computer in $computers) {
if (!(Test-Connection -ComputerName $computer -Count 1 -Quiet)) {
Write-Warning "Computer '$computer' is not responding"
continue # skip this computer and proceed with the next
}
Invoke-Command -ComputerName $computer -ScriptBlock {
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
# create a temporary Hashtable to store the items
$hash = [ordered]#{}
# loop over the properties
foreach ($prop in $using:propertynames) {
$entry = Get-ItemProperty -Path $regPath -Name $prop -ErrorAction SilentlyContinue
if ($entry) {
$hash['ComputerName'] = $using:computer
$entry = $entry | Select-Object * -ExcludeProperty PS*
# use a loop in case you have used wildards for the property names
foreach ($item in $entry.PsObject.Properties) {
$hash[$item.Name] = $item.Value
}
}
else {
Write-Warning "Could not find property '$prop'"
}
}
if ($hash.Count) {
# output the hash converted to PSObject
[PsCustomObject]$hash
}
}
}
# remove the properties added by Invoke-Command
$result = $result | Select-Object * -ExcludeProperty PS*,RunspaceId
# output to gridview
$result | Out-GridView
# output to CSV file
$result | Export-Csv -Path 'X:\Path\To\TheResults.csv' -NoTypeInformation

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

Powershell: Export User Rights Assignment

I'm new to PowerShell (PS). Currently I'm using windows server 2012 and I'm interested to know whether there is any way to export User Rights Assignment into a txt file. I tried
secedit /export /areas USER_RIGHTS /cfg d:\policies.txt
The above should should export it.
So, I get this: Current Output.
Is there any way to export User Rights Assignment and make it look like (even with using batch files): Expected Output.
P.S
Is There anyway to output those values in console? So i would be enable to redirect them to a txt file.
Here's a PowerShell script that outputs usable objects with translated names and SIDs:
#requires -version 2
# Fail script if we can't find SecEdit.exe
$SecEdit = Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::System)) "SecEdit.exe"
if ( -not (Test-Path $SecEdit) ) {
Write-Error "File not found - '$SecEdit'" -Category ObjectNotFound
exit
}
# LookupPrivilegeDisplayName Win32 API doesn't resolve logon right display
# names, so use this hashtable
$UserLogonRights = #{
"SeBatchLogonRight" = "Log on as a batch job"
"SeDenyBatchLogonRight" = "Deny log on as a batch job"
"SeDenyInteractiveLogonRight" = "Deny log on locally"
"SeDenyNetworkLogonRight" = "Deny access to this computer from the network"
"SeDenyRemoteInteractiveLogonRight" = "Deny log on through Remote Desktop Services"
"SeDenyServiceLogonRight" = "Deny log on as a service"
"SeInteractiveLogonRight" = "Allow log on locally"
"SeNetworkLogonRight" = "Access this computer from the network"
"SeRemoteInteractiveLogonRight" = "Allow log on through Remote Desktop Services"
"SeServiceLogonRight" = "Log on as a service"
}
# Create type to invoke LookupPrivilegeDisplayName Win32 API
$Win32APISignature = #'
[DllImport("advapi32.dll", SetLastError=true)]
public static extern bool LookupPrivilegeDisplayName(
string systemName,
string privilegeName,
System.Text.StringBuilder displayName,
ref uint cbDisplayName,
out uint languageId
);
'#
$AdvApi32 = Add-Type advapi32 $Win32APISignature -Namespace LookupPrivilegeDisplayName -PassThru
# Use LookupPrivilegeDisplayName Win32 API to get display name of privilege
# (except for user logon rights)
function Get-PrivilegeDisplayName {
param(
[String] $name
)
$displayNameSB = New-Object System.Text.StringBuilder 1024
$languageId = 0
$ok = $AdvApi32::LookupPrivilegeDisplayName($null, $name, $displayNameSB, [Ref] $displayNameSB.Capacity, [Ref] $languageId)
if ( $ok ) {
$displayNameSB.ToString()
}
else {
# Doesn't lookup logon rights, so use hashtable for that
if ( $UserLogonRights[$name] ) {
$UserLogonRights[$name]
}
else {
$name
}
}
}
# Outputs list of hashtables as a PSObject
function Out-Object {
param(
[System.Collections.Hashtable[]] $hashData
)
$order = #()
$result = #{}
$hashData | ForEach-Object {
$order += ($_.Keys -as [Array])[0]
$result += $_
}
New-Object PSObject -Property $result | Select-Object $order
}
# Translates a SID in the form *S-1-5-... to its account name;
function Get-AccountName {
param(
[String] $principal
)
if ( $principal[0] -eq "*" ) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($principal.Substring(1))
$sid.Translate([Security.Principal.NTAccount])
}
else {
$principal
}
}
$TemplateFilename = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
$LogFilename = Join-Path ([IO.Path]::GetTempPath()) ([IO.Path]::GetRandomFileName())
$StdOut = & $SecEdit /export /cfg $TemplateFilename /areas USER_RIGHTS /log $LogFilename
if ( $LASTEXITCODE -eq 0 ) {
Select-String '^(Se\S+) = (\S+)' $TemplateFilename | Foreach-Object {
$Privilege = $_.Matches[0].Groups[1].Value
$Principals = $_.Matches[0].Groups[2].Value -split ','
foreach ( $Principal in $Principals ) {
Out-Object `
#{"Privilege" = $Privilege},
#{"PrivilegeName" = Get-PrivilegeDisplayName $Privilege},
#{"Principal" = Get-AccountName $Principal}
}
}
}
else {
$OFS = ""
Write-Error "$StdOut"
}
Remove-Item $TemplateFilename,$LogFilename -ErrorAction SilentlyContinue
in addition to Eric's change i also needed to add a try catch to one of the functions in Bill_Stewart's post. if the SID being translated is from an object that no longer exists this will return the SID instead of sending an error for translate.
# Translates a SID in the form *S-1-5-... to its account name;
function Get-AccountName {
param(
[String] $principal
)
if ( $principal[0] -eq "*" ) {
$sid = New-Object System.Security.Principal.SecurityIdentifier($principal.Substring(1))
Try {$out = $sid.Translate([Security.Principal.NTAccount])}
catch
{
$out = $principal
}
$out
}
else {
$principal
}
}
Great script overall. Thank you for your efforts. One change I needed to make however to get it to output all principals assigned a right was to change the regex to '^(Se\S+) = (.+)' so that principals that were already resolved with a space in the name such as 'Domain users' were matched. Before that it would just report 'Domain.'
To save the output to a file, add a >> filename after the closing bracket of the last foreach-object
Ex:
}
} >> 'outFile.txt'
or to output as delimited file (e.g., csv) use the following:
} | convertto-csv -delimiter '~' -notypeinformation >> 'outFile.txt'
Hope this helps.

Active Directory and Powershell

I'm currently rewriting a script that is in VB into a Powershell script.
What the script does is search our Active Directory for a user based on the script-users input.
Function PromptForName{
$nameInput = "*"
$nameInput += Read-Host ("Please enter a full or partial name.")
$nameInput += "*"
return $nameInput
}
Function FindUsers{
param ([string]$n)
$usersArray = Get-ADUser -f {DisplayName -like $n} | Select-Object Name
return $usersArray
}
This code will print out the correct list of names. What I then want to do is allow the user to choose one of those names, and have more information about that person. I'm stuck at allowing the script-user to select one of those names.
How can I prompt for another input; where the box will display a numbered list of all the names that FindUsers gave, and then return a number based on which user they chose? I'm completely lost.
This is currently how I am trying to do it, although I'm pretty sure it's completely wrong.
Function PrintUsers{
param $a
[int]$i, $j
[string]$userList
$j = 1
foreach($object in $array){
$userList += ($j + $array[$i])
$j++
}
return $userList
}
Function SelectUser{
param $list
$user = Read-Host ($list)
}
EDIT:
I have updated my functions to the following:
Function FindUsers{
param ([string]$n)
$usersArray = #(Get-ADUser -f {DisplayName -like $n} | Select-Object Name| Format-List)
return $usersArray
}
Function PrintUsers{
param ([String[]]$array)
$i
for($i = 1; $i -lt $usersArray.length; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
}
The output after FindUsers is like this:
Name : xxxxx yyyyy
Name : xxxxx zzzzz
etc.
So the return of $usersArray is printing all that.
I don't want any printing until the PrintUsers function, and I want it to be in a numbered list type format like this:
1. xxxx yyyy
2. xxxx zzzz
etc.
I'm having the most difficult time figuring this out.
# get all users
$usersArray = #(Get-ADUser -f {DisplayName -like $n} )
# create menu
for($i=1; $i -le $usersArray.count; $i++){
Write-Host "$i. $($usersArray[$i-1].Name)"
}
# prompt for user number
$user = Read-Host Enter the user number to get more info
# display full info for selected user
$usersArray[$user-1] | Format-List *
Use Add-Member to add a unique identifier to each user. Let's treat processes as if they're user objects, for the sake of example:
$procs = gps;
$procs = $procs | % { $i=0; } {
Add-Member -MemberType NoteProperty -InputObject $_ -Name Number -Value $i -PassThru;
$i++;
};
$procs | select Number,Name;
$procid = Read-Host -Prompt 'Enter the number of the process you would like to operate on';
$proc = $procs | ? { $_.Number -eq $procid };
Write-Host -Object ('Operating on proc: {0}' -f $proc.Name);

Resources