Refresh a variable within a foreach statement - powershell

We have a master registry within our on premise SharePoint environment, to keep this list accurate, we run a clean up script each day to remove the item within the list registry.
As part of this script, we wish to delete the AD permissions group at the same time.
Our script works when deleting a single site collection, but when multiple are deleted, the script removes the list item, but attempts to delete the same AD group over and over, it doesnt refresh the variables.
Here is part of the script:
Add-PSSnapin *sharepoint* -ErrorAction SilentlyContinue
Import-Module ActiveDirectory
#Adding variables
$siteRequestUrl = "https://contorso/sites/demo"
$siteRequestListTitle = "Registry"
$webAppArray = #()
$listSCArray = #()
$itemsToDeleteArray = #()
$dn = Get-ADDoman | select -ExpandProperty DistinguishedName
$ou = Get-ADOrganizationalUnit ('Ou=SharePoint, OU=Account Groups, OU=Groups,' + $dn)
$ouSDL = Get-ADOrganizationalUnit ('Ou=SharePoint, OU=Resource Groups, OU=Groups,' + $dn)
Start-SPAssignment -Global
$list = (Get-SPSite -Identity $siteRequestUrl).RootWeb.Lists | Where-Object { $_.Title -eq $siteRequestListTitle }
$expectedSCCount = $list.ItemCount
$WebAppSCCount = $list.ParentWeb.Site.WebApplication.Sites
If ($expectedSCCount -ne $webAppSCCount) {
$listItems = $list.Items
foreach ($webAppSC in $list.ParentWeb.Site.WebApplication.Sites) {
$webAppSCArray += $webAppSC.Url
}
foreach ($item in $listItems) {
$li = New-Object Microsoft.SharePoint.SPFieldUrlValue($item["URL_Link"])
$listSCArray += $i.Url.String()
}
$comparison = Compare-Object -ReferenceObject $webAppSCArray -DifferenceObject $listSCArray | where-Object -FilterScript { $_.SiteIndicator -eq '=>' }
foreach ($difference in $comparison) {
foreach ($item in $listItems) {
$i = New-Object Microsoft.SharePoint.SPFieldUrlValue($item["URL_Link"])
If ($i.Url -eq $difference.InputObject) {
$itemsToDeleteArray = + $item
}
}
}
$itemTitle - $itemToDelete.Title.ToString()
$itemTemplate - $itemToDelete["Template"].ToString()
###### Now I move onto the problem, these variables work but do not not refresh if more than one item is being deleted ############
foreach ($itemToDelete in $itemToDeleteArray) {
switch ($itemTemplate) {
"Branch1" {
$managedPath = "BR1"
}
"Branch2" {
$managedPath = "BR2"
}
}
$forceLowerCase = $itemTitle.ToLower()
$siteTitle = $forceLowerCase -replace '\W', '-'
$GroupName = $managedPath + "-" + $siteTitle
$SggGroupName = "SGG_" + $GroupName + "_Members"
$SdlGroupName = "SDL_" + $GroupName + "_Members"
Try {
Get-ADGroup -Filter 'GroupCatergory -eq "Security" -and GroupScope -ne "DomainLocal"' -SearchBase "CN=$SggGroupName,OU=SharePoint,OU=Account Groups,OU=Groups,DC=Contorso" | Remove-ADGroup -Confirm:$false
Get-ADGroup -Filter 'GroupCatergory -eq "Security" -and GroupScope -eq "DomainLocal"' -SearchBase "CN=$SdlGroupName,OU=SharePoint,OU=Resource Groups,OU=Groups,DC=Contorso" | Remove-ADGroup -Confirm:$false
Write-host "AD Groups $SggGroupName and $SdlGroupName deleted" -ForegroundColor -Green
}
Catch { write-host "AD Groups $SggGroupName and $SdlGroupName failed to delete" }
$itemToDelete.Delete()
}
}
Stop-SPAssignment -Global

Related

How to dynamically move computer accounts from Computers OU

Its pretty "simple" what i want to achieve. I have people creating Computer Objects on my AD and leaving there without moving them to the appropiate OU.
I would like a powershell script to read the list of computers from the Computers OU, and depending the first 5 or 6 letters from the Computer name, move it to the appropiate OU, reading the list of destination OUs from a CSV or txt or whatever file type.
I need to move more than 100 computers and I would like to scan them, and move them to their corresponding OU.
I've thought to use a variable for the computer accounts, then a foreach and a switch or something similar, and 1-by-1 start moving the accounts. But I'm stuck.
Thank you!!!!
Turning my comment into an answer. You could create a lookup Hashtable for this:
# create a lookup Hashtable for all OU's in your organisation
# You can limit this using parameters like '-SearchScope' and '-SearchBase' depending on the structure in your AD environment
$allOUs = #{}
Get-ADOrganizationalUnit -Filter 'Name -like "*"' | ForEach-Object {
$allOUs[$_.Name] = $_.DistinguishedName
}
# next, get all computers in the default Computers OU
$result = Get-ADComputer -Filter * -SearchBase "CN=Computers,DC=Contoso,DC=com" | ForEach-Object {
$computerName = $_.Name
$found = $false
if ($computerName.Length -ge 6) {
$targetOU = $computerName.Substring(0,6)
$found = $allOUs.ContainsKey($targetOU)
}
if (!$found -and $computerName.Length -ge 5) {
$targetOU = $computerName.Substring(0,5)
$found = $allOUs.ContainsKey($targetOU)
}
if ($found) {
try {
$_ | Move-ADObject -TargetPath $allOUs[$targetOU] -ErrorAction Stop -WhatIf
# add success to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Moved'
}
}
catch {
# add exception to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = $targetOU
'Result' = 'Not moved. {0}' -f $_.Exception.Message
}
}
}
else {
# add failure to the $result
[PsCustomObject]#{
'Computer' = $computerName
'TargetOU' = ''
'Result' = 'Not moved. Computername does not begin with a valid OU name'
}
}
}
# output on screen
$result
# output to file
$result | Export-Csv -Path 'ComputersMoved.CSV' -NoTypeInformation
Remove the -WhatIf switch if you are satisfied with the results shown in the console.
This should be dynamic enough. You can replace the Map object with a CSV.
$Map = [PSCustomObject]#{
AABBCC = "OU=ABC,DC=Contoso,DC=com";
CCBBAA = "OU=CBA,DC=Contoso,DC=com"
}
$Prefixlist = ($Map.PSObject.Members | Where-Object { $_.MemberType -eq "NoteProperty" }).Name
$Report = #()
$MissingPrefix = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
if ($Prefixlist -contains $Prefixlist) {
try {
$obj | Move-AdObject -Targetpath $Map.$Prefix -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
else {
$MissingPrefix += $Prefixlist
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize
"Not found prefix list"
$MissingPrefix
Option 2 to make the path based on the prefix
$Report = #()
Get-ADComputer -filter * -searchbase "CN=Computers,DC=Contoso,DC=com" -Properties Name | ForEach-Object {
$obj = $_
$Prefix = ($obj.Name).Substring(0, 6)
try {
$obj | Move-AdObject -Targetpath "OU=Computers,OU=$Prefix,DC=Contoso,DC=com" -erroraction stop
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $true
}
}
catch {
$_.Exception.ErrorRecord
$Report += [PSCustomObject]#{
Name = $Obj.Name
Move = $false
}
}
}
"Result"
$Report | Format-Table -AutoSize

Modifying PowerShell to export Windows Update to CSV file for specific OU only not working

I need to get the list of the server last Windows Update patch from multiple different OU and then export it as in CSV file with the below column and its sample result I gather manually running Get-HotFix locally on each server:
ServerName, Last Time Update Installed, KB Number, KB Update Name, InstalledBy
PRODSQL01-VM, 31/12/2018 02:46:55, KB4462930, Cumulative Update, NT AUTHORITY\SYSTEM
PRODSQL02-VM, 18/12/2018 12:00:00 AM, KB4471324, Security Update, DOMAIN\SVC_SCCM
PRODDC01-VM, 16/1/2019 02:16:31, KB4343669, Cumulative Update, DOMAIN\SVC_SCCM
PRODDC02-VM, 13/1/2018 03:00:00 AM, KB4457146, Security Update, DOMAIN\Admin-Staff1
This is the modified script for multiple OU processing, but somehow the result is still a blank CSV file:
$CsvFile = 'C:\Result.csv'
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
$OUList | ForEach-Object {
$OU = $_
$Computers = Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
$_
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
ForEach ($computer in $Computers) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $computer)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
Write-Host ''
Write-Host "$($computer): last time updates were installed was $($keyValue)"
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
} | Export-Csv -Path $Csvfile -NoTypeInformation
As others have mentioned, you're doing write-host rather than adding anything to your CSV file.
Note that I haven't tested any of the below code - it's just a bit of rearranging.
$CsvFile = 'C:\Result.csv'
$Results = #() #object to hold the output
$key = 'SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\Results\Install'
$keytype = [Microsoft.Win32.RegistryHive]::LocalMachine
$OUList = #(
"OU=TEST Servers,OU=MyDomain Testing,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=MyDomain Sydney,DC=MyDomain,DC=com"
"OU=PROD Servers,OU=Servers,OU=New Company,DC=MyDomain,DC=com"
)
ForEach ($OU in $OUList) {
Get-ADComputer -Filter {Enabled -eq $True -and OperatingSystem -like "*Server*"} -SearchBase $OU |
Select-Object -ExpandProperty DNSHostName |
ForEach-Object {
If (Test-Connection $_ -Count 1 -Quiet) {
Try {
$remoteBase = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey($keytype, $_)
$regKey = $remoteBase.OpenSubKey($key)
$keyValue = $regkey.GetValue('LastSuccessTime')
# Add result to Results array. Not Result
$results += [pscustomobject]#{
Computer = $_
LastSuccessTime = $KeyValue
}
}
Catch {
$ | Write-Error
}
Finally {
If ($regKey) {$regKey.Close()}
}
}
Else {
Write-Host "Cannot reach $($_)" -ForegroundColor Red
}
}
}
#export result object to CSV
$Results | Export-Csv -Path $Csvfile -NoTypeInformation

Powershell move AD object

i made this script to find all windows 10 machines which are not placed in the right OU, there is no action taken at this point - but i would like to move them once they had been found, we have over 30 country's and data centers so i would like to keep the OU string in the arrays, to keep the code to a minimal - how would one perform a move in this script? i could use some pointers.
$Script:OUBase = "OU=Countries,OU=Global,DC=internal"
Import-Module ActiveDirectory
$CountryDataCenter =
#(
[pscustomobject]#{Country="UK";DataCenter="CEN1"},
[pscustomobject]#{Country="UK";DataCenter="CEN2"}
)
Function GetWin10MachineAccounts($Country, $DataCenter){
#Build OUstring
$OUStringTarget = "*OU=Windows 10,OU=Computers,OU=" + $DataCenter + ",OU=" + $Country + "," + $Script:OUBase
$OUStringSource = "OU=Computers,OU=" + $DataCenter + ",OU=" + $Country + "," + $Script:OUBase
$countPC = ($Win10Computeraccounts).count
Write-Host "OU to search - " $OUStringSource -ForegroundColor Yellow
$Win10ComputerAccounts = Get-ADComputer -SearchBase $OUStringSource -Filter {(enabled -eq "true") -and (OperatingSystem -like "*Windows 10*")} -properties * | where {$_.DistinguishedName -notlike "$OUStringTarget"} | select CN -expandproperty Name
Return $Win10Computeraccounts
}
############### Main Script ##########################
##create empty array for use later
$DataArray = #()
ForEach ($Country in $CountryDataCenter)
{
$Win10Computeraccounts = GetWin10MachineAccounts $Country.Country $Country.DataCenter
$countPC = $Win10Computeraccounts.count
if(!$Win10Computeraccounts) {
write-host "No Windows 10 Computers are found in the container" $Country.Country $Country.DataCenter
}
foreach ($Computer in $Win10Computeraccounts){
Write-Host $Computer -ForegroundColor Red
#Store Data in foreach array
$DataArray += (Get-ADComputer $Computer )
Write-Host "$countPC" "Computers found in" $Country.Country $Country.DataCenter -ForegroundColor Green
}
}
$DataArray | Export-Csv "C:\log.csv" -Force
Use the Move-ADObject cmdlet:
foreach($Country in $CountryDataCenter)
{
$OUStringTarget = "OU=Windows 10,OU=Computers,OU={0},OU={1},{2}" -f $Country.DataCenter,$Country.Country,$Script:OUBase
$Win10Computeraccounts = GetWin10MachineAccounts $Country.Country $Country.DataCenter
foreach ($Computer in $Win10Computeraccounts){
Move-ADObject -Identity $Computer -TargetPath $OUStringTarget
}
}

Powershell: Get Mailbox Permissions - only from ADUsers with a givenname + surname

I need to make a CSV file with the name of the mailbox (mb Identity), user, accessrights and deny. But I only want to that for ADUsers who have a givenname AND a surname not only a surname.
I thought of something like that:
$File_Path = $args[0]
$File_Path = ((Get-Item -Path ".\" -Verbose).FullName) + "\" + $File_Path
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
[System.IO.File]::WriteAllLines($File_Path, "Mailbox;RightHolder;Rights;Deny", $Utf8NoBomEncoding)
$Mailboxes = Get-Mailbox -ResultSize Unlimited
Foreach ($Mailbox in $Mailboxes) {
$mbPermissions = get-mailboxpermission $Mailbox
$str_DN = $Mailbox.DistinguishedName
$ad_obj = [ADSI]"GC://$str_DN"
Foreach ($mbPermission in $mbPermissions) {
If ($mbPermission.IsInherited -eq $False -and $mbPermission.User -notlike "NT-AUTORITÄT\SELBST") {
[System.IO.File]::AppendAllText($File_Path, "$($mbPermission.Identity);$($mbPermission.User);$($mbPermission.AccessRights);$($mbPermission.Deny)`r", $Utf8NoBomEncoding)
}
}
$ADPermissions = get-ADPermission $Mailbox.Identity
Foreach ($ADPermission in $ADPermissions) {
If ($ADPermission.ExtendedRights -like "Send-As" -and $ADPermission.User -notlike "NT-AUTORITÄT\SELBST" -and $ADPermission.Deny -eq $false) {
[System.IO.File]::AppendAllText($File_Path, "$($ADPermission.Identity);$($ADPermission.User);$($ADPermission.ExtendedRights);$($ADPermission.Deny)`r", $Utf8NoBomEncoding)
}
}
}
This works perfectly fine for creating the csv file with all credentials I need, but it doesn't exclude mailboxes from users who have no givenname. I'm a little bit stuck here.
Thanks for your help!
EDIT:
Solved it! Just put these lines instead of the $Mailboxes part:
$Mailboxes = get-mailbox -ResultSize Unlimited | select -ExpandProperty samaccountname
$Filter = foreach ($Obj in $Mailboxes) { get-aduser $Obj | select -property givenname,samaccountname }
$NoGivenName = $Filter | where { $_.givenname -ne $null } | select -ExpandProperty samaccountname
$BoxesFiltered = foreach ($Box in $NoGivenName) { get-mailbox $Box }
Can you try the following, it may take a little longer as the it has to filter out with a Get-ADUser to regenerate the list.
Import-Module ActiveDirectory
$File_Path = $args[0]
$File_Path = ((Get-Item -Path ".\" -Verbose).FullName) + "\" + $File_Path
$Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding($False)
[System.IO.File]::WriteAllLines($File_Path, "Mailbox;RightHolder;Rights;Deny", $Utf8NoBomEncoding)
$Mailboxes = Get-Mailbox -ResultSize Unlimited
$Mailboxes = Foreach ($Mailbox in $Mailbox){ (get-aduser $mailbox | where { ([string]::IsNullOrEmpty($_.givenname) -eq $false) }).samaccountname }
Foreach ($Mailbox in $Mailboxes)
{
$mbPermissions = get-mailboxpermission $Mailbox
$str_DN = $Mailbox.DistinguishedName
$ad_obj = [ADSI]"GC://$str_DN"
Foreach ($mbPermission in $mbPermissions)
{
If ($mbPermission.IsInherited -eq $False -and $mbPermission.User -notlike "NT-AUTORITÄT\SELBST")
{
[System.IO.File]::AppendAllText($File_Path, "$($mbPermission.Identity);$($mbPermission.User);$($mbPermission.AccessRights);$($mbPermission.Deny)`r", $Utf8NoBomEncoding)
}
}
$ADPermissions = get-ADPermission $Mailbox.Identity
Foreach ($ADPermission in $ADPermissions)
{
If ($ADPermission.ExtendedRights -like "Send-As" -and $ADPermission.User -notlike "NT-AUTORITÄT\SELBST" -and $ADPermission.Deny -eq $false)
{
[System.IO.File]::AppendAllText($File_Path, "$($ADPermission.Identity);$($ADPermission.User);$($ADPermission.ExtendedRights);$($ADPermission.Deny)`r", $Utf8NoBomEncoding)
}
}
}

Read from Excel and import into Active Directory

I have an Excel file which has two columns: 1st column(employees) and the second column (abbreviation).
The goal is to fill the initials in AD with the abbreviations oft he employees.
I think the PowerShell script should read the 1st column, check if the 1st column is the same as name in AD, then reads the employee's abbreviation and fill it in AD.
This is my first Powershell script and here is what I tried but of course it does not work:
$xl = New-Object -COM "Excel.Application"
$xl.Visible = $false
$wb = $xl.Workbooks.Open("C:\Users\user\Desktop\test.xlsx")
$ws = $wb.Sheets.Item(1)
for ($i = 2; $i -le 150) {
if ($ws.Cells.Item($i, 1).Value2 -ne $null) {
$Employees = $ws.Cells.Item($i, 1).Value2
$ abbreviation = $ws.Cells.Item($i, 2).Value2
write-host " Employees: "$ Employees
write-host " abbreviation: "$ abbreviation
}
$i++
}
get-aduser -Filter * -Properties initials | select name, initials
ForEach ($Employees in $name)
{
if ($Employees -eq $name)
{
#$Initials -eq $abbreviation
Set-ADUser -Initials $ abbreviation
}
}
If your just trying to update the initials with the abbreviation field from the file I would save the file as a CSV file initially and then do the following.
You can obviously add in more prompts and error checking along the way.
Get-Content test.csv | Select-Object -Skip 1 | ConvertFrom-Csv -Header Employees, abbreviation |
ForEach-Object {
Try
{
Get-ADUser $_.Employees | Set-ADUser -Initials $_.abbreviation
}
catch
{
Write-Host "Update Failed"
Write-Host "$Error[0]"
}
}
If there is no header in the file then you can drop the Select-object -skip 1
Or simpler
$Users = Import-Csv Test.csv
foreach ($User in $Users)
{
Get-ADUser $User.Employees | Set-ADUser -Initials $User.abbreviation
}
# This script reads Excel and update the Initials in AD
Import-Module ActiveDirectory;
$PathToXlsx = "C:\test.xlsx";
$xl = New-Object -COM "Excel.Application";
$xl.Visible = $false;
$wb = $xl.Workbooks.Open($PathToXlsx);
$ws = $wb.Sheets.Item(1);
for ($i = 2; $i -le 150;$i++) {
if ($ws.Cells.Item($i, 1).Value2 -ne $null) {
$Employee = $ws.Cells.Item($i, 1).Value2;
$Abbreviation = $ws.Cells.Item($i, 2).Value2;
Write-Host $Employee;
Write-Host $Abbreviation;
try
{Get-AdUser -Filter 'DisplayName -like $Employee' | Set-ADUser -Initials $Abbreviation;
$User = Get-AdUser -Filter 'DisplayName -like $Employee' -Properties 'Initials' | Select Name,Initials | Format-List;
Write-Host $User;
}
catch
{
Write-Host "Update Failed"
Write-Host "$Error[0]"
}
}
}
and in case of CSV.
Import-Module ActiveDirectory;
$PathTocsv = "C:\test.csv";
$Users = Import-Csv $PathTocsv -Delimiter ";";
foreach ($user in $Users){
$Employee = $user.Employee;
$Abbreviation = $user.Abbreviation;
Write-Host $Employee;
Write-Host $Abbreviation;
Get-AdUser -Filter 'DisplayName -like $Employee' | Set-AdUser -Initials $Abbreviation;
}