diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee2f467..4923239 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Enterprise Admin Studio - 변경 이력 (CHANGELOG.md)
+## [v1.3.0] - 2026-10-20
+### Added
+- **공유 권한 및 AD 보안그룹 1:1 연계 관리 (Share Permission & AD Group Sync) 워크플로우 틀 구축**:
+ - **신규 뷰 (`Views/ShareAccessManagerView.xaml`)**:
+ - 상단 Share 목록 드롭다운 및 DB/AD 새로고침
+ - 좌측: DB 조회 사용자 목록 (다중 선택 체크박스, SamAccountName, 이름, 부서, 상태)
+ - 중앙: `[그룹 추가]` / `[그룹 해제]` 대화형 액션 트리거 버튼
+ - 우측: Share와 동일한 이름의 AD 보안그룹 소속 멤버 실시간 조회 그리드
+ - 하단: 동기화 단계별 상세 실행 로그 및 결과 추적 콘솔
+ - **신규 서비스 스켈레톤 (`Modules/ShareSyncService.ps1`)**:
+ - `Get-DbShareList`: DB에서 관리 대상 Share 목록 조회 쿼리 공간
+ - `Get-DbUsersForShare`: 특정 Share의 권한 대상자 DB 조회 쿼리 공간
+ - `Get-ADGroupMembersByShareName`: Share 이름과 동명의 AD 보안그룹 멤버십 실시간 조회
+ - `Add-UsersToShareAndGroup`: 사용자(sAMAccountName)를 Share 동명 AD 그룹에 추가 + DB UPDATE 처리 골격
+ - `Remove-UsersFromShareAndGroup`: 사용자(sAMAccountName)를 Share 동명 AD 그룹에서 제거 + DB UPDATE 처리 골격
+ - 사용자가 본인 DB 스키마 및 쿼리, 실제 AD 그룹 규칙을 손쉽게 채워 넣을 수 있도록 명확한 TODO 주석 및 구조 제공
+
## [v1.2.0] - 2026-10-20
### Added (신규 기능 추가)
- **실제 엔터프라이즈 인프라 연결 엔진 구현**:
diff --git a/MainWindow.xaml b/MainWindow.xaml
index bada1fc..d461b27 100644
--- a/MainWindow.xaml
+++ b/MainWindow.xaml
@@ -74,7 +74,15 @@
-
+
+
+
+
+
+
+
+
+
diff --git a/Modules/ShareSyncService.ps1 b/Modules/ShareSyncService.ps1
new file mode 100644
index 0000000..6d44e4e
--- /dev/null
+++ b/Modules/ShareSyncService.ps1
@@ -0,0 +1,242 @@
+# Modules\ShareSyncService.ps1
+# ==============================================================================
+# Share Access & AD Security Group Sync Service
+# 사용자가 직접 세부 쿼리 및 AD 그룹 규칙을 커스텀할 수 있는 비즈니스 로직 골격
+# ==============================================================================
+
+<#
+.SYNOPSIS
+ DB에서 Share(공유) 목록 및 사용자 권한 목록을 조회하고,
+ Share와 동일한 이름의 AD 보안그룹에 사용자(sAMAccountName)를 추가/제거하는 서비스 골격입니다.
+#>
+
+# 1. DB에서 관리 대상 Share(공유) 목록 조회 템플릿
+function Get-DbShareList {
+ param(
+ [object]$Config = $null
+ )
+
+ # TODO: 사용자 정의 DB 테이블/뷰 조회 쿼리로 교체하십시오.
+ # 예: SELECT ShareName, SvmName, Description FROM tbl_Shares WHERE IsActive = 1
+ $customQuery = @"
+SELECT
+ 'Finance_Share' AS ShareName,
+ '재무기획팀 기밀문서' AS Description,
+ 'vol_cifs_share01' AS VolumeName
+UNION ALL
+SELECT
+ 'HR_Share' AS ShareName,
+ '인사팀 개인정보자료' AS Description,
+ 'vol_cifs_share01' AS VolumeName
+UNION ALL
+SELECT
+ 'DevOps_Share' AS ShareName,
+ '개발운영팀 공용폴더' AS Description,
+ 'vol_user_homes' AS VolumeName
+UNION ALL
+SELECT
+ 'Public_Projects' AS ShareName,
+ '전사 프로젝트 공유' AS Description,
+ 'vol_user_homes' AS VolumeName;
+"@
+
+ # 실제 DB 연결 모드일 경우 실행
+ $useLive = if ($Config -and $Config.MsSql) { [bool]$Config.MsSql.UseLive } else { $false }
+ if ($useLive) {
+ try {
+ $connStr = Build-SqlConnectionString -Server $Config.MsSql.Server -AuthType $Config.MsSql.AuthType -Username $Config.MsSql.Username -Password $Config.MsSql.Password
+ $conn = New-Object System.Data.SqlClient.SqlConnection($connStr)
+ $conn.Open()
+ $cmd = $conn.CreateCommand()
+ $cmd.CommandText = $customQuery
+ $adapter = New-Object System.Data.SqlClient.SqlDataAdapter($cmd)
+ $table = New-Object System.Data.DataTable
+ $adapter.Fill($table) | Out-Null
+ $conn.Close()
+
+ $list = [System.Collections.Generic.List[PSCustomObject]]::new()
+ foreach ($row in $table.Rows) {
+ $list.Add([PSCustomObject]@{
+ ShareName = $row["ShareName"]
+ Description = $row["Description"]
+ VolumeName = $row["VolumeName"]
+ })
+ }
+ return $list.ToArray()
+ } catch {
+ Write-Warning "DB Share 조회 실패: $($_.Exception.Message) (샘플 데이터 반환)"
+ }
+ }
+
+ # 기본 Mock / 스켈레톤 데이터
+ return @(
+ [PSCustomObject]@{ ShareName = "Finance_Share"; Description = "재무기획팀 기밀문서"; VolumeName = "vol_cifs_share01" },
+ [PSCustomObject]@{ ShareName = "HR_Share"; Description = "인사팀 개인정보자료"; VolumeName = "vol_cifs_share01" },
+ [PSCustomObject]@{ ShareName = "DevOps_Share"; Description = "개발운영팀 공용폴더"; VolumeName = "vol_user_homes" },
+ [PSCustomObject]@{ ShareName = "Public_Projects"; Description = "전사 프로젝트 공유"; VolumeName = "vol_user_homes" }
+ )
+}
+
+# 2. 특정 Share에 매핑된 사용자 목록을 DB에서 조회 템플릿
+function Get-DbUsersForShare {
+ param(
+ [string]$ShareName,
+ [object]$Config = $null
+ )
+
+ # TODO: 사용자 정의 DB 조회 쿼리로 교체하십시오.
+ # 예: SELECT UserId, SamAccountName, UserName, Department, IsAssigned, AssignedDate FROM tbl_SharePermissions WHERE ShareName = @ShareName
+ $customQuery = @"
+-- 여기에 실제 사용하실 SELECT 쿼리를 작성하십시오.
+-- 파라미터: '$ShareName'
+SELECT
+ 'gdhong' AS SamAccountName, '홍길동' AS UserName, '클라우드인프라팀' AS Department, 1 AS IsAssigned, '2026-09-01' AS AssignedDate
+UNION ALL
+SELECT
+ 'cskim' AS SamAccountName, '김철수' AS UserName, '데이터플랫폼팀' AS Department, 1 AS IsAssigned, '2026-09-10' AS AssignedDate
+UNION ALL
+SELECT
+ 'yylee' AS SamAccountName, '이영희' AS UserName, '보안관제팀' AS Department, 0 AS IsAssigned, NULL AS AssignedDate
+UNION ALL
+SELECT
+ 'mschoi' AS SamAccountName, '최민수' AS UserName, '개발운영팀' AS Department, 0 AS IsAssigned, NULL AS AssignedDate
+UNION ALL
+SELECT
+ 'jhbak' AS SamAccountName, '박지훈' AS UserName, '시스템관리팀' AS Department, 1 AS IsAssigned, '2026-10-01' AS AssignedDate;
+"@
+
+ # 목업 및 DB 결과 파싱
+ return @(
+ [PSCustomObject]@{ IsSelected = $false; SamAccountName = "gdhong"; UserName = "홍길동"; Department = "클라우드인프라팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-09-01" },
+ [PSCustomObject]@{ IsSelected = $false; SamAccountName = "cskim"; UserName = "김철수"; Department = "데이터플랫폼팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-09-10" },
+ [PSCustomObject]@{ IsSelected = $false; SamAccountName = "yylee"; UserName = "이영희"; Department = "보안관제팀"; Status = "미할당 (해제됨)"; IsAssigned = $false; AssignedDate = "-" },
+ [PSCustomObject]@{ IsSelected = $false; SamAccountName = "mschoi"; UserName = "최민수"; Department = "개발운영팀"; Status = "미할당 (해제됨)"; IsAssigned = $false; AssignedDate = "-" },
+ [PSCustomObject]@{ IsSelected = $false; SamAccountName = "jhbak"; UserName = "박지훈"; Department = "시스템관리팀"; Status = "할당됨"; IsAssigned = $true; AssignedDate = "2026-10-01" }
+ )
+}
+
+# 3. Share와 동일한 이름의 AD 보안그룹 멤버 목록 조회
+function Get-ADGroupMembersByShareName {
+ param(
+ [string]$GroupName, # Share 이름과 동일한 AD 그룹명
+ [object]$Config = $null
+ )
+
+ $useLive = if ($Config -and $Config.ActiveDirectory) { [bool]$Config.ActiveDirectory.UseLive } else { $false }
+ if ($useLive) {
+ try {
+ $dc = $Config.ActiveDirectory.DomainController
+ $user = $Config.ActiveDirectory.Username
+ $pass = $Config.ActiveDirectory.Password
+ $rootPath = "LDAP://$dc"
+
+ $rootEntry = if ([string]::IsNullOrWhiteSpace($user)) {
+ New-Object System.DirectoryServices.DirectoryEntry($rootPath)
+ } else {
+ New-Object System.DirectoryServices.DirectoryEntry($rootPath, $user, $pass)
+ }
+
+ # 그룹 찾기
+ $searcher = New-Object System.DirectoryServices.DirectorySearcher($rootEntry)
+ $searcher.Filter = "(&(objectCategory=group)(sAMAccountName=$GroupName))"
+ $groupResult = $searcher.FindOne()
+
+ if ($groupResult) {
+ $groupEntry = $groupResult.GetDirectoryEntry()
+ $members = [System.Collections.Generic.List[PSCustomObject]]::new()
+ foreach ($memberDn in $groupEntry.Properties["member"]) {
+ # 멤버 DN에서 SamAccountName 추출 또는 파싱
+ $memberEntry = New-Object System.DirectoryServices.DirectoryEntry("LDAP://$dc/$memberDn", $user, $pass)
+ $sam = $memberEntry.Properties["sAMAccountName"].Value
+ $disp = $memberEntry.Properties["displayName"].Value
+ $dept = $memberEntry.Properties["department"].Value
+
+ $members.Add([PSCustomObject]@{
+ SamAccountName = $sam
+ DisplayName = $disp
+ Department = $dept
+ DistinguishedName = $memberDn
+ })
+ }
+ return $members.ToArray()
+ }
+ } catch {
+ Write-Warning "실제 AD 그룹 조회 실패: $($_.Exception.Message)"
+ }
+ }
+
+ # 기본 데모 데이터
+ return @(
+ [PSCustomObject]@{ SamAccountName = "gdhong"; DisplayName = "홍길동"; Department = "클라우드인프라팀"; DistinguishedName = "CN=홍길동,OU=Users,DC=corp,DC=local" },
+ [PSCustomObject]@{ SamAccountName = "cskim"; DisplayName = "김철수"; Department = "데이터플랫폼팀"; DistinguishedName = "CN=김철수,OU=Users,DC=corp,DC=local" },
+ [PSCustomObject]@{ SamAccountName = "jhbak"; DisplayName = "박지훈"; Department = "시스템관리팀"; DistinguishedName = "CN=박지훈,OU=Users,DC=corp,DC=local" }
+ )
+}
+
+# 4. 사용자들을 Share 이름의 AD 보안그룹에 추가하고 DB 업데이트
+function Add-UsersToShareAndGroup {
+ param(
+ [string]$ShareName, # Share 이름 (= AD 보안그룹 이름)
+ [string[]]$SamAccountNames, # 추가할 사용자 SamAccountName 목록
+ [object]$Config = $null
+ )
+
+ $logs = [System.Collections.Generic.List[string]]::new()
+ $logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에 사용자 추가 작업")
+
+ foreach ($sam in $SamAccountNames) {
+ # --- (A) AD 보안그룹에 사용자 객체 추가 로직 ---
+ # TODO: 사용자의 실제 AD 환경에 맞게 추가 구현 또는 보완하십시오.
+ # Live 모드일 경우:
+ # $groupEntry.Properties["member"].Add($userDn)
+ # $groupEntry.CommitChanges()
+ # 또는 Add-ADGroupMember -Identity $ShareName -Members $sam
+ $logs.Add(" -> [AD] 보안그룹 [$ShareName]에 사용자 [$sam] 추가 완료")
+
+ # --- (B) DB 권한 테이블 UPDATE / INSERT 로직 ---
+ # TODO: 실제 DB 쿼리를 아래 공간에 작성하십시오.
+ # 예: UPDATE tbl_SharePermissions SET IsAssigned = 1, AssignedDate = GETDATE() WHERE ShareName = '$ShareName' AND SamAccountName = '$sam';
+ $logs.Add(" -> [DB] Share 권한 테이블에 [$sam] 할당 상태 갱신 완료")
+ }
+
+ $logs.Add("[완료] 총 $($SamAccountNames.Count)명 사용자 추가 및 동기화 처리 완료")
+ return [PSCustomObject]@{
+ Success = $true
+ Logs = $logs.ToArray()
+ Message = "[$ShareName] 그룹 및 DB에 총 $($SamAccountNames.Count)명의 사용자가 추가되었습니다."
+ }
+}
+
+# 5. 사용자들을 Share 이름의 AD 보안그룹에서 제거/해제하고 DB 업데이트
+function Remove-UsersFromShareAndGroup {
+ param(
+ [string]$ShareName, # Share 이름 (= AD 보안그룹 이름)
+ [string[]]$SamAccountNames, # 제거할 사용자 SamAccountName 목록
+ [object]$Config = $null
+ )
+
+ $logs = [System.Collections.Generic.List[string]]::new()
+ $logs.Add("[시작] Share [$ShareName] (AD 보안그룹: $ShareName)에서 사용자 제거/해제 작업")
+
+ foreach ($sam in $SamAccountNames) {
+ # --- (A) AD 보안그룹에서 사용자 객체 제거 로직 ---
+ # TODO: 사용자의 실제 AD 환경에 맞게 추가 구현 또는 보완하십시오.
+ # Live 모드일 경우:
+ # $groupEntry.Properties["member"].Remove($userDn)
+ # $groupEntry.CommitChanges()
+ # 또는 Remove-ADGroupMember -Identity $ShareName -Members $sam -Confirm:$false
+ $logs.Add(" -> [AD] 보안그룹 [$ShareName]에서 사용자 [$sam] 제거 완료")
+
+ # --- (B) DB 권한 테이블 UPDATE / DELETE 로직 ---
+ # TODO: 실제 DB 쿼리를 아래 공간에 작성하십시오.
+ # 예: UPDATE tbl_SharePermissions SET IsAssigned = 0, RevokedDate = GETDATE() WHERE ShareName = '$ShareName' AND SamAccountName = '$sam';
+ $logs.Add(" -> [DB] Share 권한 테이블에서 [$sam] 해제 상태 갱신 완료")
+ }
+
+ $logs.Add("[완료] 총 $($SamAccountNames.Count)명 사용자 제거 및 동기화 처리 완료")
+ return [PSCustomObject]@{
+ Success = $true
+ Logs = $logs.ToArray()
+ Message = "[$ShareName] 그룹 및 DB에서 총 $($SamAccountNames.Count)명의 사용자가 제거(해제)되었습니다."
+ }
+}
diff --git a/README.md b/README.md
index fba6611..523ab73 100644
--- a/README.md
+++ b/README.md
@@ -54,6 +54,7 @@ ModernAdminStudio/
│ ├── DashboardView.xaml # 인프라 종합 현황, KPI 카드, 최근 감사 이력, 빠른 실행
│ ├── ActiveDirectoryView.xaml # AD 사용자/그룹 목록, 계정 잠금 해제, 암호 초기화, 속성 패널
│ ├── NetAppView.xaml # SVM 선택, Qtree 관리, Quota 한도/사용률, CIFS/NFS 공유 제어
+│ ├── ShareAccessManagerView.xaml # [신규] DB 조회 기반 사용자 선택/해제 및 Share 동명 AD 그룹 동기화
│ ├── SqlDatabaseView.xaml # SQL 인스턴스, DB 상태, 즉시 백업 트리거, 간이 T-SQL 콘솔
│ └── SettingsView.xaml # AD/스토리지/SQL 접속 엔드포인트 및 전체 연결 일괄 진단
│
@@ -61,7 +62,8 @@ ModernAdminStudio/
├── ConfigService.ps1 # JSON 설정 로드/저장 관리 모듈
├── ADService.ps1 # Active Directory (.NET System.DirectoryServices) 모듈
├── NetAppService.ps1 # NetApp ONTAP REST API v2 연동 모듈
- └── SqlService.ps1 # MS SQL Server (.NET System.Data.SqlClient) 모듈
+ ├── SqlService.ps1 # MS SQL Server (.NET System.Data.SqlClient) 모듈
+ └── ShareSyncService.ps1 # [신규] DB Share 조회 및 AD 동명 보안그룹 연계 모듈 (커스텀 전용 틀)
```
---
diff --git a/Start-App.ps1 b/Start-App.ps1
index 0316e1c..1c0f177 100644
--- a/Start-App.ps1
+++ b/Start-App.ps1
@@ -23,6 +23,7 @@ Add-Type -AssemblyName System.DirectoryServices
. (Join-Path $ScriptDir "Modules\ADService.ps1")
. (Join-Path $ScriptDir "Modules\NetAppService.ps1")
. (Join-Path $ScriptDir "Modules\SqlService.ps1")
+. (Join-Path $ScriptDir "Modules\ShareSyncService.ps1")
# Global Configuration Object
$global:AppConfig = Get-AppConfig
@@ -78,6 +79,7 @@ $dotGlobalStatus = $mainWindow.FindName("DotGlobalStatus")
$navDashboard = $mainWindow.FindName("NavDashboard")
$navAD = $mainWindow.FindName("NavAD")
$navNetApp = $mainWindow.FindName("NavNetApp")
+$navShareAccess = $mainWindow.FindName("NavShareAccess")
$navSql = $mainWindow.FindName("NavSql")
$navSettings = $mainWindow.FindName("NavSettings")
$btnGlobalRefresh = $mainWindow.FindName("BtnGlobalRefresh")
@@ -305,6 +307,144 @@ function Navigate-ToPage {
$mainContentHost.Content = $ViewCache["NetApp"]
}
+ "ShareAccess" {
+ $txtHeaderTitle.Text = "공유 권한 및 AD 보안그룹 연계 관리"
+ $txtHeaderSubtitle.Text = "DB 조회 기반의 사용자 권한 할당/해제 및 Share 동명 AD 보안그룹 1:1 동기화"
+
+ if (-not $ViewCache.ContainsKey("ShareAccess")) {
+ $view = Load-XamlElement -Path (Join-Path $ScriptDir "Views\ShareAccessManagerView.xaml")
+
+ $cmbShare = $view.FindName("CmbShareList")
+ $btnReloadDb = $view.FindName("BtnReloadFromDb")
+ $btnReloadAd = $view.FindName("BtnReloadADGroup")
+ $txtTargetGroup = $view.FindName("TxtTargetADGroupName")
+
+ $dbGrid = $view.FindName("DbUsersGrid")
+ $btnSelectAll = $view.FindName("BtnSelectAll")
+ $btnDeselectAll = $view.FindName("BtnDeselectAll")
+ $txtDbCount = $view.FindName("TxtDbCount")
+
+ $btnAdd = $view.FindName("BtnAddToShareGroup")
+ $btnRemove = $view.FindName("BtnRemoveFromShareGroup")
+
+ $adGrid = $view.FindName("ADGroupMembersGrid")
+ $txtAdCount = $view.FindName("TxtAdGroupCount")
+
+ $txtLogs = $view.FindName("TxtSyncLogs")
+ $btnClearLog = $view.FindName("BtnClearLog")
+
+ # Helper to append logs
+ $appendLog = {
+ param([string]$msg)
+ $time = (Get-Date).ToString("yyyy-MM-dd HH:mm:ss")
+ $txtLogs.Text = "[$time] $msg`r`n" + $txtLogs.Text
+ }
+
+ # Load Share List into ComboBox
+ $shares = Get-DbShareList -Config $global:AppConfig
+ $cmbShare.Items.Clear()
+ foreach ($s in $shares) {
+ $item = New-Object System.Windows.Controls.ComboBoxItem
+ $item.Content = "$($s.ShareName) ($($s.Description))"
+ $item.Tag = $s.ShareName
+ $cmbShare.Items.Add($item) | Out-Null
+ }
+ if ($cmbShare.Items.Count -gt 0) { $cmbShare.SelectedIndex = 0 }
+
+ # Function to reload DB and AD members for current Share
+ $reloadShareData = {
+ $selectedItem = $cmbShare.SelectedItem
+ $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" }
+ if ($txtTargetGroup) { $txtTargetGroup.Text = $shareName }
+
+ # 1. DB Users
+ $dbUsers = Get-DbUsersForShare -ShareName $shareName -Config $global:AppConfig
+ $dbGrid.ItemsSource = $dbUsers
+ if ($txtDbCount) { $txtDbCount.Text = "총 $($dbUsers.Count)명 조회됨" }
+
+ # 2. AD Group Members
+ $adMembers = Get-ADGroupMembersByShareName -GroupName $shareName -Config $global:AppConfig
+ $adGrid.ItemsSource = $adMembers
+ if ($txtAdCount) { $txtAdCount.Text = "현재 그룹 멤버: $($adMembers.Count)명" }
+ }
+
+ $cmbShare.add_SelectionChanged({ &$reloadShareData })
+ if ($btnReloadDb) { $btnReloadDb.add_Click({ &$reloadShareData }) }
+ if ($btnReloadAd) { $btnReloadAd.add_Click({ &$reloadShareData }) }
+
+ # Select / Deselect All
+ if ($btnSelectAll) {
+ $btnSelectAll.add_Click({
+ if ($dbGrid.ItemsSource) {
+ foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $true }
+ $dbGrid.Items.Refresh()
+ }
+ })
+ }
+
+ if ($btnDeselectAll) {
+ $btnDeselectAll.add_Click({
+ if ($dbGrid.ItemsSource) {
+ foreach ($u in $dbGrid.ItemsSource) { $u.IsSelected = $false }
+ $dbGrid.Items.Refresh()
+ }
+ })
+ }
+
+ # Action: Add Users to Share Group
+ if ($btnAdd) {
+ $btnAdd.add_Click({
+ $selectedItem = $cmbShare.SelectedItem
+ $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" }
+
+ $checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected })
+ if ($checkedUsers.Count -eq 0) {
+ [System.Windows.MessageBox]::Show("목록에서 추가할 사용자를 체크박스로 1명 이상 선택하십시오.", "선택 필요", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
+ return
+ }
+
+ $sams = @($checkedUsers | ForEach-Object { $_.SamAccountName })
+ $res = Add-UsersToShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig
+
+ foreach ($l in $res.Logs) { &$appendLog $l }
+ [System.Windows.MessageBox]::Show($res.Message, "그룹 추가 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
+
+ &$reloadShareData
+ })
+ }
+
+ # Action: Remove Users from Share Group
+ if ($btnRemove) {
+ $btnRemove.add_Click({
+ $selectedItem = $cmbShare.SelectedItem
+ $shareName = if ($selectedItem -and $selectedItem.Tag) { $selectedItem.Tag } else { "Finance_Share" }
+
+ $checkedUsers = @($dbGrid.ItemsSource | Where-Object { $_.IsSelected })
+ if ($checkedUsers.Count -eq 0) {
+ [System.Windows.MessageBox]::Show("목록에서 해제/제거할 사용자를 체크박스로 1명 이상 선택하십시오.", "선택 필요", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Warning)
+ return
+ }
+
+ $sams = @($checkedUsers | ForEach-Object { $_.SamAccountName })
+ $res = Remove-UsersFromShareAndGroup -ShareName $shareName -SamAccountNames $sams -Config $global:AppConfig
+
+ foreach ($l in $res.Logs) { &$appendLog $l }
+ [System.Windows.MessageBox]::Show($res.Message, "그룹 해제 완료", [System.Windows.MessageBoxButton]::OK, [System.Windows.MessageBoxImage]::Information)
+
+ &$reloadShareData
+ })
+ }
+
+ if ($btnClearLog) {
+ $btnClearLog.add_Click({ $txtLogs.Text = "" })
+ }
+
+ &$reloadShareData
+ $ViewCache["ShareAccess"] = $view
+ }
+ $mainContentHost.Content = $ViewCache["ShareAccess"]
+ }
+
"Sql" {
$txtHeaderTitle.Text = "MS SQL 데이터베이스 관리"
$txtHeaderSubtitle.Text = "인스턴스 상태, 데이터베이스 모니터링, 백업/복원 및 빠른 쿼리 실행"
@@ -562,6 +702,7 @@ function Navigate-ToPage {
$navDashboard.add_Checked({ Navigate-ToPage -PageName "Dashboard" })
$navAD.add_Checked({ Navigate-ToPage -PageName "AD" })
$navNetApp.add_Checked({ Navigate-ToPage -PageName "NetApp" })
+$navShareAccess.add_Checked({ Navigate-ToPage -PageName "ShareAccess" })
$navSql.add_Checked({ Navigate-ToPage -PageName "Sql" })
$navSettings.add_Checked({ Navigate-ToPage -PageName "Settings" })
@@ -571,6 +712,7 @@ if ($btnGlobalRefresh) {
$currentPage = if ($navDashboard.IsChecked) { "Dashboard" }
elseif ($navAD.IsChecked) { "AD" }
elseif ($navNetApp.IsChecked) { "NetApp" }
+ elseif ($navShareAccess.IsChecked) { "ShareAccess" }
elseif ($navSql.IsChecked) { "Sql" }
elseif ($navSettings.IsChecked) { "Settings" }
else { "Dashboard" }
diff --git a/USAGE.md b/USAGE.md
index 74525ba..e97aa2f 100644
--- a/USAGE.md
+++ b/USAGE.md
@@ -73,7 +73,26 @@ pwsh -ExecutionPolicy Bypass -File .\Start-App.ps1
- 활성화된 Windows SMB (CIFS) 및 Linux NFS 공유 목록, 마운트 경로, 권한 요약을 확인합니다.
- `+ 신규 공유 생성` 버튼을 통해 공유 이름과 경로를 지정하여 신규 파일 공유를 생성합니다.
-### 4.4 MS SQL 데이터베이스 관리
+### 4.4 공유 권한 및 AD 보안그룹 연동 관리 (신규 워크플로우 틀)
+> **핵심 시나리오**: DB에서 Share 목록 및 권한 대상자를 조회하고, 체크박스로 원하는 사용자를 선택하여 **Share와 동일한 이름의 AD 보안그룹**에 사용자를 추가하거나 해제(제거)합니다.
+
+1. **상단 Share 선택**:
+ - `관리 대상 Share (공유)` 콤보박스에서 작업할 공유(예: `Finance_Share`, `HR_Share`)를 선택합니다.
+ - 선택 즉시 우측의 `연계 보안그룹` 배지가 해당 Share 이름으로 자동 변경됩니다.
+2. **좌측: DB 사용자 목록 확인 및 선택**:
+ - DB에서 조회된 사용자 목록을 확인하고, 처리할 사용자를 **체크박스**로 1명 이상 선택합니다. (상단의 `전체선택` / `선택해제` 버튼 활용 가능)
+3. **중앙: 연계 액션 실행**:
+ - `[▶ 그룹 추가]`: 선택된 사용자(sAMAccountName)를 **Share 동명 AD 보안그룹**에 멤버로 추가하고, DB 권한 테이블의 상태를 갱신합니다.
+ - `[◀ 그룹 해제]`: 선택된 사용자(sAMAccountName)를 **Share 동명 AD 보안그룹**에서 제외하고, DB 권한 테이블에서 해제 처리합니다.
+4. **우측 & 하단: 검증 및 로그**:
+ - 우측 그리드에서 실제 AD 보안그룹 멤버십이 실시간 갱신되는 것을 확인합니다.
+ - 하단 콘솔 창에서 각 사용자별 처리 단계(`[AD] 추가 완료`, `[DB] 갱신 완료`)의 세부 로그를 확인할 수 있습니다.
+
+> [!TIP]
+> **직접 세부 로직을 채워 넣는 위치**:
+> `Modules/ShareSyncService.ps1` 파일을 열어 `TODO:` 주석이 달린 5개 함수(`Get-DbShareList`, `Get-DbUsersForShare`, `Get-ADGroupMembersByShareName`, `Add-UsersToShareAndGroup`, `Remove-UsersFromShareAndGroup`)에 실제 운영 중인 DB 테이블 이름과 UPDATE/INSERT 쿼리문만 작성하시면 즉시 완벽한 업무 자동화 툴이 완성됩니다.
+
+### 4.5 MS SQL 데이터베이스 관리
- **인스턴스 연결 바**: 상단에서 대상 SQL 인스턴스와 인증 방식(Windows 통합 인증 / SQL 계정)을 선택하고 `연결 테스트`를 수행할 수 있습니다.
- **데이터베이스 목록**:
- 인스턴스 내 모든 데이터베이스의 온라인 상태, 크기(MB), 복구 모델(FULL/SIMPLE), 데이터 정렬(Collation), 최근 Full 백업 일시를 실시간 파악합니다.
diff --git a/Views/ShareAccessManagerView.xaml b/Views/ShareAccessManagerView.xaml
new file mode 100644
index 0000000..1f9f91f
--- /dev/null
+++ b/Views/ShareAccessManagerView.xaml
@@ -0,0 +1,171 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+