Office365-REST-Python-Client 3.1.0__py3-none-any.whl → 3.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- office365/communications/callrecords/call_record.py +18 -0
- office365/communications/calls/call.py +3 -0
- office365/communications/presences/presence.py +3 -0
- office365/communications/quotas.py +66 -0
- office365/communications/virtualevents/virtual_event.py +3 -0
- office365/directory/domains/domain.py +3 -0
- office365/directory/licenses/subscribed_sku.py +3 -0
- office365/directory/objects/object.py +3 -0
- office365/directory/quotas.py +34 -0
- office365/entity_collection.py +2 -2
- office365/graph_client.py +46 -2
- office365/limits.py +66 -0
- office365/migration/__init__.py +6 -1
- office365/migration/adapters/__init__.py +14 -4
- office365/migration/adapters/filesystem.py +4 -1
- office365/migration/assessment/export.py +5 -5
- office365/migration/assessment/issue.py +19 -0
- office365/migration/assessment/report.py +11 -0
- office365/migration/assessment/scanners/base.py +37 -49
- office365/migration/base.py +33 -8
- office365/migration/checkpoint.py +1 -1
- office365/migration/outlook/assessor.py +4 -4
- office365/migration/package/__init__.py +82 -0
- office365/migration/package/builder.py +603 -0
- office365/migration/package/crypto.py +54 -0
- office365/migration/package/manifest_xml.py +495 -0
- office365/migration/package/staging.py +180 -0
- office365/migration/report.py +7 -2
- office365/migration/report_io.py +79 -0
- office365/migration/runner.py +117 -10
- office365/migration/server_job.py +126 -14
- office365/migration/session.py +132 -49
- office365/migration/settings.py +67 -0
- office365/migration/sharepoint/__init__.py +2 -0
- office365/migration/sharepoint/adapters.py +207 -41
- office365/migration/sharepoint/assessor.py +45 -9
- office365/migration/sharepoint/options.py +57 -0
- office365/migration/sharepoint/package_target.py +209 -0
- office365/migration/sharepoint/registry.py +22 -0
- office365/migration/sharepoint/resolvers.py +96 -0
- office365/migration/sharepoint/scanners/__init__.py +29 -0
- office365/migration/sharepoint/scanners/browser_file_handling.py +55 -0
- office365/migration/sharepoint/scanners/checked_out_files.py +57 -0
- office365/migration/sharepoint/scanners/file_versions.py +56 -0
- office365/migration/sharepoint/scanners/files.py +5 -2
- office365/migration/sharepoint/scanners/large_excel_files.py +56 -0
- office365/migration/sharepoint/scanners/large_lists.py +58 -0
- office365/migration/sharepoint/scanners/large_sites.py +4 -3
- office365/migration/sharepoint/scanners/long_onedrive_urls.py +52 -0
- office365/migration/sharepoint/scanners/lookup.py +38 -0
- office365/migration/sharepoint/scanners/paths.py +9 -4
- office365/migration/sharepoint/scanners/permissions.py +20 -2
- office365/migration/sharepoint/scanners/smat.py +51 -0
- office365/migration/sharepoint/scanners/summary.py +1 -0
- office365/migration/sharepoint/scanners/thicket_folders.py +38 -0
- office365/migration/sharepoint/scanners/unsupported_site_templates.py +90 -0
- office365/migration/sharepoint/tenant_assessor.py +5 -3
- office365/migration/sharepoint/transfer.py +4 -2
- office365/migration/tasks.py +121 -0
- office365/migration/validators.py +2 -19
- office365/onedrive/driveitems/driveItem.py +116 -12
- office365/onedrive/termstore/groups/collection.py +2 -9
- office365/onedrive/termstore/sets/collection.py +2 -9
- office365/onedrive/termstore/terms/collection.py +3 -10
- office365/onedrive/workbooks/workbook.py +7 -0
- office365/outlook/contacts/folders/collection.py +4 -13
- office365/runtime/auth/authentication_context.py +5 -0
- office365/runtime/client_object.py +18 -0
- office365/runtime/client_object_collection.py +75 -245
- office365/runtime/client_request.py +77 -1
- office365/runtime/client_request_exception.py +127 -39
- office365/runtime/client_runtime_context.py +79 -8
- office365/runtime/converters/csv_writer.py +10 -4
- office365/runtime/converters/dataframe.py +84 -4
- office365/runtime/converters/json_file.py +12 -3
- office365/runtime/converters/parquet.py +67 -0
- office365/runtime/converters/records.py +31 -12
- office365/runtime/converters/registry.py +185 -0
- office365/runtime/converters/scalars.py +31 -0
- office365/runtime/converters/sql.py +69 -0
- office365/runtime/converters/streamers.py +105 -0
- office365/runtime/converters/streams.py +42 -0
- office365/runtime/converters/upsert.py +101 -0
- office365/runtime/exceptions.py +105 -0
- office365/runtime/http/prefer.py +26 -0
- office365/runtime/http/throttling.py +258 -56
- office365/runtime/imports.py +571 -0
- office365/runtime/limits.py +455 -0
- office365/runtime/odata/request.py +11 -2
- office365/runtime/odata/type.py +1 -0
- office365/runtime/odata/v3/batch_request.py +4 -2
- office365/runtime/odata/v4/batch_request.py +22 -7
- office365/runtime/operations.py +29 -0
- office365/runtime/parallel.py +12 -14
- office365/runtime/paths/v4/entity.py +2 -2
- office365/runtime/queries/batch.py +18 -1
- office365/runtime/queries/get_or_create.py +109 -0
- office365/runtime/record_collection.py +443 -0
- office365/runtime/retry.py +92 -26
- office365/runtime/transport/base.py +6 -1
- office365/runtime/transport/requests_transport.py +4 -0
- office365/runtime/transport/throttled_transport.py +67 -0
- office365/runtime/verification.py +40 -0
- office365/sharepoint/activities/logger.py +118 -8
- office365/sharepoint/administration/analytics/usage_service.py +15 -7
- office365/sharepoint/administration/sitemove/service.py +791 -8
- office365/sharepoint/alerts/alert.py +79 -0
- office365/sharepoint/apps/app_collection.py +25 -0
- office365/sharepoint/apps/launcher.py +16 -0
- office365/sharepoint/authpolicy/tenant/compliancereport.py +5 -0
- office365/sharepoint/businessdata/app_bdc_catalog.py +135 -0
- office365/sharepoint/businessdata/entityfieldvaluedictionary.py +65 -0
- office365/sharepoint/businessdata/entityidentifier.py +23 -0
- office365/sharepoint/businessdata/entityinstance.py +52 -0
- office365/sharepoint/businessdata/entityview.py +31 -0
- office365/sharepoint/businessdata/externalsubscriptionstore.py +9 -0
- office365/sharepoint/businessdata/typedescriptor.py +37 -0
- office365/sharepoint/client_context.py +40 -23
- office365/sharepoint/clientsidecomponent/hostedapps/app.py +21 -0
- office365/sharepoint/clientsidecomponent/hostedapps/manager.py +16 -0
- office365/sharepoint/comments/comment.py +84 -2
- office365/sharepoint/contentcenter/machinelearning/models/model.py +0 -23
- office365/sharepoint/contentcenter/machinelearning/publications/publication.py +0 -9
- office365/sharepoint/contentcenter/machinelearning/samples/sample.py +0 -14
- office365/sharepoint/contentcenter/machinelearning/workitems/item.py +0 -80
- office365/sharepoint/contentsolution/template_api.py +148 -0
- office365/sharepoint/contenttypes/collection.py +32 -10
- office365/sharepoint/convergence/addnoliveblobsinodcservicestateapi.py +9 -0
- office365/sharepoint/convergence/consumer_permissions.py +15 -0
- office365/sharepoint/convergence/migration_complete_state_api.py +25 -0
- office365/sharepoint/convergence/odcmetadatacleanedupapi.py +9 -0
- office365/sharepoint/documentmanagement/document_id.py +12 -0
- office365/sharepoint/entity.py +11 -2
- office365/sharepoint/entity_collection.py +2 -2
- office365/sharepoint/exceptions.py +194 -0
- office365/sharepoint/fields/builtin_field_id.py +45 -34
- office365/sharepoint/fields/builtin_field_name.py +123 -0
- office365/sharepoint/fields/coercion.py +131 -0
- office365/sharepoint/fields/collection.py +54 -37
- office365/sharepoint/fields/field.py +17 -0
- office365/sharepoint/fields/name.py +46 -2
- office365/sharepoint/files/collection.py +7 -1
- office365/sharepoint/files/file.py +54 -8
- office365/sharepoint/files/versions/version.py +38 -13
- office365/sharepoint/folders/collection.py +4 -0
- office365/sharepoint/folders/folder.py +91 -21
- office365/sharepoint/htmlpages/html_page.py +16 -0
- office365/sharepoint/htmlpages/html_page_service.py +19 -0
- office365/sharepoint/ir/migration.py +9 -0
- office365/sharepoint/listitems/caml/__init__.py +31 -0
- office365/sharepoint/listitems/caml/builder.py +129 -0
- office365/sharepoint/listitems/caml/expressions.py +424 -0
- office365/sharepoint/listitems/caml/fields.py +68 -0
- office365/sharepoint/listitems/caml/guard.py +63 -0
- office365/sharepoint/listitems/caml/query.py +71 -2
- office365/sharepoint/listitems/caml/values.py +113 -0
- office365/sharepoint/listitems/collection.py +21 -20
- office365/sharepoint/listitems/upsert.py +75 -0
- office365/sharepoint/lists/collection.py +29 -22
- office365/sharepoint/lists/exporter.py +4 -3
- office365/sharepoint/lists/list.py +397 -39
- office365/sharepoint/lists/smart_wiki_library_utility.py +58 -0
- office365/sharepoint/logger/export.py +12 -0
- office365/sharepoint/marketplace/app_metadata.py +97 -1
- office365/sharepoint/marketplace/appstatequeryjobdefinition.py +9 -0
- office365/sharepoint/marketplace/corporatecuratedgallery/carddesigns/card_designs.py +35 -0
- office365/sharepoint/marketplace/sitecollection/appcatalog/allowed_item.py +5 -0
- office365/sharepoint/marketplace/tenant/appcatalog/accessor.py +107 -30
- office365/sharepoint/microfeed/data.py +0 -6
- office365/sharepoint/microservice/manager.py +61 -0
- office365/sharepoint/migrationcenter/common/task.py +193 -4
- office365/sharepoint/migrationcenter/common/task_entity_data.py +1 -1
- office365/sharepoint/migrationcenter/deploy_status.py +13 -0
- office365/sharepoint/migrationcenter/service/properties.py +20 -0
- office365/sharepoint/migrationcenter/service/storage.py +34 -0
- office365/sharepoint/migrationcenter/spflight.py +13 -0
- office365/sharepoint/migrationcenter/urlparseresult.py +77 -0
- office365/sharepoint/multigeo/service/spmultigeoproxy.py +13 -0
- office365/sharepoint/navigation/servicerest.py +127 -0
- office365/sharepoint/oauth/authentication.py +13 -0
- office365/sharepoint/portal/theme_manager.py +120 -8
- office365/sharepoint/principal/users/user.py +10 -0
- office365/sharepoint/publishing/campaign/campaign.py +36 -1
- office365/sharepoint/publishing/feedvideopage.py +0 -7
- office365/sharepoint/publishing/pages/3d/page.py +0 -6
- office365/sharepoint/publishing/pages/page.py +0 -117
- office365/sharepoint/publishing/pages/reposts/repost.py +0 -7
- office365/sharepoint/publishing/pages/topics/topic.py +0 -7
- office365/sharepoint/publishing/subtitlefile.py +0 -40
- office365/sharepoint/quotamanagement/consumer/consumer_quota_info_api.py +14 -0
- office365/sharepoint/quotamanagement/consumer/non_quota_backfill_api.py +23 -0
- office365/sharepoint/quotamanagement/consumer/nonquotamigrationapi.py +9 -0
- office365/sharepoint/quotamanagement/consumer/quotamigrationapi.py +13 -0
- office365/sharepoint/request.py +57 -16
- office365/sharepoint/search/administration/document_crawl_log.py +24 -7
- office365/sharepoint/search/administration/providers/crawl_versions_info.py +29 -0
- office365/sharepoint/search/administration/providers/recycle_bin_info.py +23 -0
- office365/sharepoint/search/administration/providers/site_content_processing_info.py +67 -0
- office365/sharepoint/sites/site.py +976 -65
- office365/sharepoint/sites/siteprovisioninginfoprovider.py +13 -0
- office365/sharepoint/tenant/administration/datagovernance/insight_client.py +292 -23
- office365/sharepoint/tenant/administration/ddiadapter.py +97 -0
- office365/sharepoint/tenant/administration/internal/aad/collection.py +20 -0
- office365/sharepoint/tenant/administration/internal/aad/permission_grant.py +0 -19
- office365/sharepoint/tenant/administration/internal/appservice/permission_request.py +72 -1
- office365/sharepoint/tenant/administration/internal/appservice/principal.py +1 -3
- office365/sharepoint/tenant/administration/odb_license_enforcement_service.py +26 -0
- office365/sharepoint/tenant/administration/sites/properties.py +564 -6
- office365/sharepoint/tenant/administration/spdatagovernancesarrestapiclient.py +60 -1
- office365/sharepoint/tenant/insights/reports/manager.py +24 -12
- office365/sharepoint/tenant/management/office365_tenant.py +451 -2
- office365/sharepoint/tenantadministration/catalog_management_collection.py +69 -0
- office365/sharepoint/tenantadministration/catalog_management_custom_properties_collection.py +237 -0
- office365/sharepoint/tenantadministration/catalog_management_restricted_content_discovery_collection.py +56 -0
- office365/sharepoint/tenantadministration/sp_data_governance_client_exception.py +7 -0
- office365/sharepoint/thresholds.py +529 -0
- office365/sharepoint/translation/jobs/job.py +72 -2
- office365/sharepoint/translation/jobs/status.py +47 -9
- office365/sharepoint/translation/sync_translator.py +15 -2
- office365/sharepoint/utilities/move_copy_util.py +2 -1
- office365/sharepoint/viva/connections/page.py +27 -0
- office365/sharepoint/viva/home.py +38 -0
- office365/sharepoint/viva/learningmanager.py +9 -0
- office365/sharepoint/viva/vivaresources.py +60 -0
- office365/sharepoint/webhooks/subscription.py +0 -11
- office365/sharepoint/webparts/definitions/definition.py +13 -0
- office365/sharepoint/webparts/limited_manager.py +29 -7
- office365/sharepoint/webs/context_web_information.py +14 -9
- office365/sharepoint/webs/templates/__init__.py +11 -0
- office365/sharepoint/webs/templates/type.py +74 -15
- office365/sharepoint/workflow/definition.py +6 -0
- office365/sharepoint/workflow/deployment_service.py +155 -0
- office365/sharepoint/workflow/instances/service.py +33 -0
- office365/sharepoint/workflow/interopservice.py +68 -0
- office365/sharepoint/workflow/messagingservice.py +24 -0
- office365/sharepoint/workflow/servicesmanager.py +41 -2
- office365/sharepoint/workflow/subscriptions/service.py +67 -0
- office365/sharepoint/workflow/subscriptions/subscription.py +31 -0
- office365/sharepoint/workflow/task.py +425 -0
- office365_rest_python_client-3.2.0.dist-info/METADATA +605 -0
- {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/RECORD +242 -185
- office365/migration/_util.py +0 -73
- office365/sharepoint/listitems/caml/types.py +0 -70
- office365_rest_python_client-3.1.0.dist-info/METADATA +0 -475
- {office365_rest_python_client-3.1.0.dist-info → office365_rest_python_client-3.2.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""Checked-out files scan — the SMAT ``CheckedOutFiles`` report.
|
|
2
|
+
|
|
3
|
+
Only checked-in content is migrated: a file a user has checked out is read as its
|
|
4
|
+
last checked-in version, so uncommitted edits are lost. This ``ITEMS``-container
|
|
5
|
+
report lists the files that are checked out and by whom, and raises a per-list
|
|
6
|
+
warning (data-loss risk).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
14
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
15
|
+
from office365.migration.sharepoint.scanners.smat import SiteScanRecord, site_url
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class CheckedOutFilesRecord(SiteScanRecord):
|
|
20
|
+
"""One row of the SMAT ``CheckedOutFiles-detail`` report."""
|
|
21
|
+
|
|
22
|
+
File: str | None = None
|
|
23
|
+
CheckedOutUser: str | None = None
|
|
24
|
+
ScanID: str | None = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CheckedOutFilesScanner(BaseScanner[CheckedOutFilesRecord]):
|
|
28
|
+
"""Reports checked-out files (SMAT ``CheckedOutFiles``) and warns per list."""
|
|
29
|
+
|
|
30
|
+
category = "file"
|
|
31
|
+
scan_name = "CheckedOutFiles"
|
|
32
|
+
record_type = CheckedOutFilesRecord
|
|
33
|
+
|
|
34
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
35
|
+
page: list[CheckedOutFilesRecord] = []
|
|
36
|
+
for item in target.entity:
|
|
37
|
+
file = getattr(item, "file", None)
|
|
38
|
+
if file is None or not file.check_out_type:
|
|
39
|
+
continue # 0/None = not checked out
|
|
40
|
+
user = file.checked_out_by_user
|
|
41
|
+
page.append(
|
|
42
|
+
CheckedOutFilesRecord(
|
|
43
|
+
SiteURL=site_url(target.location),
|
|
44
|
+
File=item.properties.get("FileRef", ""),
|
|
45
|
+
CheckedOutUser=(user.login_name or user.title) if user is not None else None,
|
|
46
|
+
ScanID=report.scan_id or None,
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
if page:
|
|
50
|
+
self.records.extend(page)
|
|
51
|
+
self.flag(
|
|
52
|
+
report,
|
|
53
|
+
"warning",
|
|
54
|
+
target.location or "list",
|
|
55
|
+
f"{len(page)} checked-out file(s) — only the last checked-in version migrates",
|
|
56
|
+
"Ask users to check in their files before migrating",
|
|
57
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""File versions scan — the SMAT ``FileVersions`` report (files with version history).
|
|
2
|
+
|
|
3
|
+
Version history grows a migration's runtime roughly linearly, so before migrating
|
|
4
|
+
it is worth knowing which files carry versions. This ``ITEMS``-container report
|
|
5
|
+
scan lists each file that has more than its current version, using the file's
|
|
6
|
+
``MajorVersion``/``MinorVersion`` (loaded with the item's ``File`` expand).
|
|
7
|
+
|
|
8
|
+
``VersionCount`` is ``major + minor`` — the current version level, a close
|
|
9
|
+
approximation of version-history depth for major-only versioning. Site-level SMAT
|
|
10
|
+
columns that only exist on-premises (``ContentDB*``, usage-logging) are ``None``
|
|
11
|
+
and exported as ``n/a``.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
|
|
18
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
19
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
20
|
+
from office365.migration.sharepoint.scanners.smat import SiteScanRecord, site_url
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class FileVersionsRecord(SiteScanRecord):
|
|
25
|
+
"""One row of the SMAT ``FileVersions-detail`` report."""
|
|
26
|
+
|
|
27
|
+
VersionCount: int | None = None
|
|
28
|
+
File: str | None = None
|
|
29
|
+
ScanID: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FileVersionsScanner(BaseScanner[FileVersionsRecord]):
|
|
33
|
+
"""Reports files that have version history (SMAT ``FileVersions``)."""
|
|
34
|
+
|
|
35
|
+
category = "file"
|
|
36
|
+
scan_name = "FileVersions"
|
|
37
|
+
record_type = FileVersionsRecord
|
|
38
|
+
|
|
39
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
40
|
+
location = site_url(target.location)
|
|
41
|
+
for item in target.entity:
|
|
42
|
+
file = getattr(item, "file", None)
|
|
43
|
+
if file is None:
|
|
44
|
+
continue
|
|
45
|
+
major = file.major_version or 0
|
|
46
|
+
minor = file.minor_version or 0
|
|
47
|
+
if major <= 1 and minor == 0:
|
|
48
|
+
continue # only the current version — no history
|
|
49
|
+
self.records.append(
|
|
50
|
+
FileVersionsRecord(
|
|
51
|
+
SiteURL=location,
|
|
52
|
+
VersionCount=major + minor,
|
|
53
|
+
File=item.properties.get("FileRef", ""),
|
|
54
|
+
ScanID=report.scan_id or None,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from office365.migration.assessment.report import AssessmentReport
|
|
6
6
|
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
7
|
+
from office365.runtime.limits import hint
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class FileScanner(BaseScanner):
|
|
@@ -12,14 +13,16 @@ class FileScanner(BaseScanner):
|
|
|
12
13
|
category = "file"
|
|
13
14
|
|
|
14
15
|
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
16
|
+
limit = self.options.limit("large_file_bytes")
|
|
15
17
|
for item in target.entity:
|
|
16
18
|
path = item.properties.get("FileRef", "")
|
|
17
19
|
size = (item.file.length if getattr(item, "file", None) is not None else None) or 0
|
|
18
|
-
if size >
|
|
20
|
+
if size > limit.value:
|
|
19
21
|
self.flag(
|
|
20
22
|
report,
|
|
21
23
|
"warning",
|
|
22
24
|
path,
|
|
23
|
-
|
|
25
|
+
hint(limit, value=size, context="file"),
|
|
24
26
|
"Use chunked upload or split the file",
|
|
27
|
+
limit=limit,
|
|
25
28
|
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Large Excel files scan — the SMAT ``LargeExcelFiles`` report.
|
|
2
|
+
|
|
3
|
+
Excel workbooks larger than 10 MB can't open in the browser on the target — users
|
|
4
|
+
are prompted to open them in the Excel client. This ``ITEMS``-container report
|
|
5
|
+
lists the affected ``.xlsx``/``.xls``/``.xlsm``/``.xlsb`` files (SMAT reports the
|
|
6
|
+
``.xlsx`` set; the common Excel extensions are included here).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
14
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
15
|
+
from office365.migration.sharepoint.scanners.smat import SiteScanRecord, file_extension, site_url
|
|
16
|
+
|
|
17
|
+
#: Excel workbook extensions included in the report.
|
|
18
|
+
EXCEL_EXTENSIONS = frozenset({".xlsx", ".xls", ".xlsm", ".xlsb"})
|
|
19
|
+
|
|
20
|
+
_MB = 1024 * 1024
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class LargeExcelFilesRecord(SiteScanRecord):
|
|
25
|
+
"""One row of the SMAT ``LargeExcelFiles-detail`` report."""
|
|
26
|
+
|
|
27
|
+
File: str | None = None
|
|
28
|
+
FileSizeinMB: float | None = None
|
|
29
|
+
ScanID: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LargeExcelFilesScanner(BaseScanner[LargeExcelFilesRecord]):
|
|
33
|
+
"""Reports Excel workbooks over the browser-open limit (SMAT ``LargeExcelFiles``)."""
|
|
34
|
+
|
|
35
|
+
category = "file"
|
|
36
|
+
scan_name = "LargeExcelFiles"
|
|
37
|
+
record_type = LargeExcelFilesRecord
|
|
38
|
+
|
|
39
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
40
|
+
limit = self.options.limit("large_excel_bytes")
|
|
41
|
+
location = site_url(target.location)
|
|
42
|
+
for item in target.entity:
|
|
43
|
+
if file_extension(item) not in EXCEL_EXTENSIONS:
|
|
44
|
+
continue
|
|
45
|
+
file = getattr(item, "file", None)
|
|
46
|
+
size = (file.length if file is not None else None) or 0
|
|
47
|
+
if size <= limit.value:
|
|
48
|
+
continue
|
|
49
|
+
self.records.append(
|
|
50
|
+
LargeExcelFilesRecord(
|
|
51
|
+
SiteURL=location,
|
|
52
|
+
File=item.properties.get("FileRef", ""),
|
|
53
|
+
FileSizeinMB=round(size / _MB, 2),
|
|
54
|
+
ScanID=report.scan_id or None,
|
|
55
|
+
)
|
|
56
|
+
)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Large list scan — the SPMT ``LIST_VIEW_EXCEED_LIMIT`` /
|
|
2
|
+
``ITEM_COUNT_EXCEED_INDEX_LIMIT`` / ``ITEM_COUNT_EXCEED_LIMIT`` readiness checks.
|
|
3
|
+
|
|
4
|
+
A ``LIST``-container scan over a list's item count, graded by the SharePoint
|
|
5
|
+
limits (``office365.sharepoint.thresholds.Limits``):
|
|
6
|
+
|
|
7
|
+
- over the list view threshold (5,000) → info (classic views throttle; migration is fine)
|
|
8
|
+
- over the index-add threshold (20,000) → warning (a column index can't be added)
|
|
9
|
+
- over the per-list maximum (30M) → blocker (migration fails)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
15
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
16
|
+
from office365.runtime.limits import hint
|
|
17
|
+
|
|
18
|
+
#: (threshold field, severity, SPMT risk code, remediation) — checked in order.
|
|
19
|
+
_CHECKS = (
|
|
20
|
+
("max_list_items", "blocker", "ITEM_COUNT_EXCEED_LIMIT", "Split or archive the list before migrating"),
|
|
21
|
+
(
|
|
22
|
+
"index_threshold",
|
|
23
|
+
"warning",
|
|
24
|
+
"ITEM_COUNT_EXCEED_INDEX_LIMIT",
|
|
25
|
+
"Migrate without adding indexes, or split the list",
|
|
26
|
+
),
|
|
27
|
+
(
|
|
28
|
+
"list_view_threshold",
|
|
29
|
+
"info",
|
|
30
|
+
"LIST_VIEW_EXCEED_LIMIT",
|
|
31
|
+
"Index the filtered/sorted columns or use the modern experience",
|
|
32
|
+
),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LargeListScanner(BaseScanner):
|
|
37
|
+
"""Flags lists whose item count crosses the list view / index / max thresholds."""
|
|
38
|
+
|
|
39
|
+
category = "list"
|
|
40
|
+
|
|
41
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
42
|
+
lst = target.entity
|
|
43
|
+
count = lst.item_count or 0
|
|
44
|
+
location = target.location or (lst.title or "list")
|
|
45
|
+
|
|
46
|
+
for field, severity, risk_code, suggestion in _CHECKS:
|
|
47
|
+
limit = self.options.limit(field)
|
|
48
|
+
if count > limit.value:
|
|
49
|
+
self.flag(
|
|
50
|
+
report,
|
|
51
|
+
severity,
|
|
52
|
+
location,
|
|
53
|
+
hint(limit, value=count, context="list"),
|
|
54
|
+
suggestion,
|
|
55
|
+
risk_code=risk_code,
|
|
56
|
+
limit=limit,
|
|
57
|
+
)
|
|
58
|
+
break
|
|
@@ -90,7 +90,8 @@ class LargeSitesScanner(BaseScanner[LargeSitesRecord]):
|
|
|
90
90
|
size_gb = (summary.storage_bytes or 0) / (1024**3) if summary.storage_bytes else None
|
|
91
91
|
size_mb = round((summary.storage_bytes or 0) / (1024**2), 1) if summary.storage_bytes else None
|
|
92
92
|
locked = summary.lock_state in {"NoAccess", "Locked"}
|
|
93
|
-
|
|
93
|
+
threshold = self.options.large_site_threshold_gb
|
|
94
|
+
over = size_gb is not None and threshold is not None and size_gb > threshold
|
|
94
95
|
|
|
95
96
|
if summary.report_impacted_only and (locked or not over):
|
|
96
97
|
return
|
|
@@ -109,12 +110,12 @@ class LargeSitesScanner(BaseScanner[LargeSitesRecord]):
|
|
|
109
110
|
row.TotalItemCount = summary.item_count
|
|
110
111
|
self.records.append(row)
|
|
111
112
|
|
|
112
|
-
if over and not summary.report_impacted_only:
|
|
113
|
+
if over and not summary.report_impacted_only and threshold is not None and size_gb is not None:
|
|
113
114
|
self.flag(
|
|
114
115
|
report,
|
|
115
116
|
"warning",
|
|
116
117
|
summary.site_url or "site",
|
|
117
|
-
f"Site size {size_gb:.1f}GB exceeds the {
|
|
118
|
+
f"Site size {size_gb:.1f}GB exceeds the {threshold:g}GB guidance — "
|
|
118
119
|
"migration takes longer to schedule and run",
|
|
119
120
|
"Split the site collection, archive old content, or store large binaries externally",
|
|
120
121
|
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Long OneDrive URLs scan — the SMAT ``LongOneDriveUrls`` report.
|
|
2
|
+
|
|
3
|
+
OneDrive and SharePoint sync fail on file paths whose decoded URL exceeds the
|
|
4
|
+
sync limit, so before a migration it is worth listing them. This ``ITEMS``
|
|
5
|
+
container report measures each file's full URL (the tenant origin plus its
|
|
6
|
+
server-relative path) against ``Limits.LONG_ONEDRIVE_URL``.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from urllib.parse import urlsplit
|
|
13
|
+
|
|
14
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
15
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
16
|
+
from office365.migration.sharepoint.scanners.smat import SiteScanRecord, site_url
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class LongOneDriveUrlsRecord(SiteScanRecord):
|
|
21
|
+
"""One row of the SMAT ``LongOneDriveUrls-detail`` report."""
|
|
22
|
+
|
|
23
|
+
File: str | None = None
|
|
24
|
+
UrlLength: int | None = None
|
|
25
|
+
ScanID: str | None = None
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LongOneDriveUrlsScanner(BaseScanner[LongOneDriveUrlsRecord]):
|
|
29
|
+
"""Reports files whose full URL is over the OneDrive sync limit (SMAT ``LongOneDriveUrls``)."""
|
|
30
|
+
|
|
31
|
+
category = "file"
|
|
32
|
+
scan_name = "LongOneDriveUrls"
|
|
33
|
+
record_type = LongOneDriveUrlsRecord
|
|
34
|
+
|
|
35
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
36
|
+
limit = self.options.limit("long_onedrive_url")
|
|
37
|
+
location = site_url(target.location)
|
|
38
|
+
parts = urlsplit(target.location or "")
|
|
39
|
+
origin = f"{parts.scheme}://{parts.netloc}" if parts.scheme and parts.netloc else ""
|
|
40
|
+
for item in target.entity:
|
|
41
|
+
path = item.properties.get("FileRef", "")
|
|
42
|
+
url = f"{origin}{path}"
|
|
43
|
+
if len(url) <= limit.value:
|
|
44
|
+
continue
|
|
45
|
+
self.records.append(
|
|
46
|
+
LongOneDriveUrlsRecord(
|
|
47
|
+
SiteURL=location,
|
|
48
|
+
File=path,
|
|
49
|
+
UrlLength=len(url),
|
|
50
|
+
ScanID=report.scan_id or None,
|
|
51
|
+
)
|
|
52
|
+
)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Lookup column scan — the SPMT ``LIST_VIEW_LOOKUP_EXCEED_LIMIT`` check.
|
|
2
|
+
|
|
3
|
+
A ``FIELDS``-container scan: displaying/querying too many lookup, person/group,
|
|
4
|
+
or managed-metadata columns trips the list view lookup threshold (8 joins
|
|
5
|
+
blocked, 12 returned), so the list view (and some tooling) can fail.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
11
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
12
|
+
from office365.runtime.limits import hint
|
|
13
|
+
|
|
14
|
+
_LOOKUP_TYPES = frozenset({"Lookup", "LookupMulti", "User", "UserMulti", "TaxonomyFieldType", "TaxonomyFieldTypeMulti"})
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class LookupColumnScanner(BaseScanner):
|
|
18
|
+
"""Flags lists whose lookup/people/managed-metadata columns exceed the lookup threshold."""
|
|
19
|
+
|
|
20
|
+
category = "field"
|
|
21
|
+
|
|
22
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
23
|
+
lookups = [
|
|
24
|
+
field.properties.get("InternalName", "")
|
|
25
|
+
for field in target.entity
|
|
26
|
+
if field.properties.get("TypeAsString") in _LOOKUP_TYPES
|
|
27
|
+
]
|
|
28
|
+
limit = self.options.limit("lookup_joins")
|
|
29
|
+
if len(lookups) > limit.value:
|
|
30
|
+
self.flag(
|
|
31
|
+
report,
|
|
32
|
+
"warning",
|
|
33
|
+
target.location,
|
|
34
|
+
hint(limit, value=len(lookups), context="list"),
|
|
35
|
+
"Reduce the lookup columns or raise the resource-throttling limit",
|
|
36
|
+
risk_code="LIST_VIEW_LOOKUP_EXCEED_LIMIT",
|
|
37
|
+
limit=limit,
|
|
38
|
+
)
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from office365.migration.assessment.report import AssessmentReport
|
|
6
6
|
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
7
|
+
from office365.runtime.limits import hint
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class PathScanner(BaseScanner):
|
|
@@ -12,26 +13,30 @@ class PathScanner(BaseScanner):
|
|
|
12
13
|
category = "path"
|
|
13
14
|
|
|
14
15
|
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
16
|
+
path_limit = self.options.limit("max_path_length")
|
|
17
|
+
name_limit = self.options.limit("max_name_length")
|
|
15
18
|
for item in target.entity:
|
|
16
19
|
path = item.properties.get("FileRef", "")
|
|
17
20
|
name = item.properties.get("FileLeafRef", "")
|
|
18
21
|
|
|
19
|
-
if len(path) >
|
|
22
|
+
if len(path) > path_limit.value:
|
|
20
23
|
self.flag(
|
|
21
24
|
report,
|
|
22
25
|
"blocker",
|
|
23
26
|
path,
|
|
24
|
-
|
|
27
|
+
hint(path_limit, value=len(path), context="path"),
|
|
25
28
|
"Shorten folder names or restructure hierarchy",
|
|
29
|
+
limit=path_limit,
|
|
26
30
|
)
|
|
27
31
|
|
|
28
|
-
if len(name) >
|
|
32
|
+
if len(name) > name_limit.value:
|
|
29
33
|
self.flag(
|
|
30
34
|
report,
|
|
31
35
|
"blocker",
|
|
32
36
|
path,
|
|
33
|
-
|
|
37
|
+
hint(name_limit, value=len(name), context="file name"),
|
|
34
38
|
"Rename file before migration",
|
|
39
|
+
limit=name_limit,
|
|
35
40
|
)
|
|
36
41
|
|
|
37
42
|
bad = [c for c in name if c in self.options.invalid_chars]
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
from office365.migration.assessment.report import AssessmentReport
|
|
6
6
|
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
7
|
+
from office365.runtime.limits import hint
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class PermissionScanner(BaseScanner):
|
|
@@ -14,11 +15,28 @@ class PermissionScanner(BaseScanner):
|
|
|
14
15
|
|
|
15
16
|
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
16
17
|
unique = sum(1 for i in target.entity if i.properties.get("HasUniqueRoleAssignments", False))
|
|
17
|
-
|
|
18
|
+
supported = self.options.limit("unique_scopes")
|
|
19
|
+
recommended = self.options.limit("recommended_unique_scopes")
|
|
20
|
+
if unique > supported.value:
|
|
21
|
+
self.flag(
|
|
22
|
+
report,
|
|
23
|
+
"blocker",
|
|
24
|
+
target.location,
|
|
25
|
+
hint(supported, value=unique, context="list"),
|
|
26
|
+
"Reduce the number of uniquely-permitted items before migrating",
|
|
27
|
+
risk_code="UNIQUE_PERMISSION_EXCEED_LIMIT",
|
|
28
|
+
limit=supported,
|
|
29
|
+
)
|
|
30
|
+
elif unique > 0:
|
|
31
|
+
over_recommended = unique > recommended.value
|
|
18
32
|
self.flag(
|
|
19
33
|
report,
|
|
20
34
|
"warning",
|
|
21
35
|
target.location,
|
|
22
|
-
|
|
36
|
+
hint(recommended, value=unique, context="list")
|
|
37
|
+
if over_recommended
|
|
38
|
+
else f"{unique} items have unique permissions",
|
|
23
39
|
"Set preserve_permissions=True in MigrationOptions (slower migration)",
|
|
40
|
+
risk_code="UNIQUE_PERMISSION_EXCEED_LIMIT",
|
|
41
|
+
limit=recommended if over_recommended else None,
|
|
24
42
|
)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Shared SMAT detail-report scaffolding.
|
|
2
|
+
|
|
3
|
+
Every SMAT ``<Scan>-detail.csv`` starts with the same site-collection context
|
|
4
|
+
columns (``SiteId … DaysOfUsageData``). :class:`SiteScanRecord` declares that
|
|
5
|
+
prefix once; each report scan subclasses it and appends its own columns (the
|
|
6
|
+
dataclass fields ARE the report columns, base-first).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
__all__ = ["SiteScanRecord", "file_extension", "site_url"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class SiteScanRecord:
|
|
20
|
+
"""The SMAT detail-report site-collection column prefix.
|
|
21
|
+
|
|
22
|
+
``None`` renders as ``n/a`` (the report convention for unavailable data);
|
|
23
|
+
the ``ContentDB*`` and usage-logging columns only exist on-premises.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
SiteId: str | None = None
|
|
27
|
+
SiteURL: str | None = None
|
|
28
|
+
SiteOwner: str | None = None
|
|
29
|
+
SiteAdmins: str | None = None
|
|
30
|
+
SiteSizeInMB: float | None = None
|
|
31
|
+
NumOfWebs: int | None = None
|
|
32
|
+
ContentDBName: str | None = None
|
|
33
|
+
ContentDBServerName: str | None = None
|
|
34
|
+
ContentDBSizeInMB: str | None = None
|
|
35
|
+
LastContentModifiedDate: datetime | None = None
|
|
36
|
+
TotalItemCount: int | None = None
|
|
37
|
+
Hits: int | None = None
|
|
38
|
+
DistinctUsers: str | None = None
|
|
39
|
+
DaysOfUsageData: str | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def file_extension(item: Any) -> str:
|
|
43
|
+
"""The item's lower-cased extension including the dot (``""`` when none)."""
|
|
44
|
+
name = item.properties.get("FileLeafRef") or ""
|
|
45
|
+
_, dot, extension = name.rpartition(".")
|
|
46
|
+
return f".{extension.lower()}" if dot and extension else ""
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def site_url(location: str | None) -> str | None:
|
|
50
|
+
"""Best-effort site/web URL from a scan location (``<web>/lists/<title>``)."""
|
|
51
|
+
return location.rsplit("/lists/", 1)[0] if location and "/lists/" in location else None
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Thicket folders scan — the SPMT ``THICKET_FOLDER_UNSUPPORTED`` risk.
|
|
2
|
+
|
|
3
|
+
Folders whose name ends in ``_file`` or ``_files`` are treated as *thicket*
|
|
4
|
+
folders — the hidden linked folder SharePoint creates to store the supporting
|
|
5
|
+
content of an HTM file. SharePoint in Microsoft 365 can't create them, so a
|
|
6
|
+
migration carrying such a folder fails. This ``ITEMS``-container scan flags each
|
|
7
|
+
one so it can be renamed first.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
13
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
14
|
+
|
|
15
|
+
#: Folder-name suffixes SharePoint treats as thicket folders.
|
|
16
|
+
_THICKET_SUFFIXES = ("_file", "_files")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ThicketFolderScanner(BaseScanner):
|
|
20
|
+
"""Flags folders whose name ends in ``_file``/``_files``."""
|
|
21
|
+
|
|
22
|
+
category = "file"
|
|
23
|
+
|
|
24
|
+
def run(self, target: ScanTarget, report: AssessmentReport) -> None:
|
|
25
|
+
for item in target.entity:
|
|
26
|
+
if getattr(item, "file", None) is not None:
|
|
27
|
+
continue # a file, not a folder
|
|
28
|
+
name = item.properties.get("FileLeafRef", "")
|
|
29
|
+
if not name.lower().endswith(_THICKET_SUFFIXES):
|
|
30
|
+
continue
|
|
31
|
+
self.flag(
|
|
32
|
+
report,
|
|
33
|
+
"blocker",
|
|
34
|
+
item.properties.get("FileRef", "") or name,
|
|
35
|
+
f"Thicket folder '{name}' isn't supported in SharePoint in Microsoft 365",
|
|
36
|
+
"Rename the folder so it no longer ends in '_file'/'_files'",
|
|
37
|
+
risk_code="THICKET_FOLDER_UNSUPPORTED",
|
|
38
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Unsupported site templates scan — the SMAT ``UnsupportedSiteTemplates`` report.
|
|
2
|
+
|
|
3
|
+
A site collection created from an on-premises-only site definition (a Central
|
|
4
|
+
Administration site, a My Site host, a meeting workspace, an Access services
|
|
5
|
+
database, ...) has no Microsoft 365 equivalent, so it can't be migrated as-is.
|
|
6
|
+
This ``SITE``-container report lists the collections whose root web template is
|
|
7
|
+
in that legacy set.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from office365.migration.assessment.report import AssessmentReport
|
|
15
|
+
from office365.migration.assessment.scanners.base import BaseScanner, ScanTarget
|
|
16
|
+
from office365.migration.sharepoint.scanners.summary import SiteScanSummary
|
|
17
|
+
from office365.sharepoint.webs.templates import WebTemplateType
|
|
18
|
+
|
|
19
|
+
#: Templates with no Microsoft 365 equivalent — a collection built from one can't
|
|
20
|
+
#: be migrated as-is. Held as template names (the part before ``#``) so the match
|
|
21
|
+
#: is independent of the site-definition configuration id.
|
|
22
|
+
_UNSUPPORTED_TEMPLATES = frozenset(
|
|
23
|
+
template.name_part.upper()
|
|
24
|
+
for template in (
|
|
25
|
+
WebTemplateType.CENTRAL_ADMIN,
|
|
26
|
+
WebTemplateType.TENANT_ADMIN,
|
|
27
|
+
WebTemplateType.SHAREPOINT_ONLINE_TENANT_ADMIN,
|
|
28
|
+
WebTemplateType.SHARED_SERVICES_ADMINISTRATION,
|
|
29
|
+
WebTemplateType.SHAREPOINT_PORTAL_SERVER_SITE,
|
|
30
|
+
WebTemplateType.SHAREPOINT_PORTAL_PERSONAL_SPACE,
|
|
31
|
+
WebTemplateType.PERSONALIZATION_SITE,
|
|
32
|
+
WebTemplateType.MY_SITE_HOST,
|
|
33
|
+
WebTemplateType.CONTENTS_AREA,
|
|
34
|
+
WebTemplateType.TOPIC_AREA,
|
|
35
|
+
WebTemplateType.NEWS_SITE,
|
|
36
|
+
WebTemplateType.NEWS_HOME,
|
|
37
|
+
WebTemplateType.SITE_DIRECTORY,
|
|
38
|
+
WebTemplateType.COMMUNITY_AREA,
|
|
39
|
+
WebTemplateType.COLLABORATION_PORTAL,
|
|
40
|
+
WebTemplateType.REPORT_CENTER,
|
|
41
|
+
WebTemplateType.PROFILES,
|
|
42
|
+
WebTemplateType.ACCESS_SERVICES,
|
|
43
|
+
WebTemplateType.ASSETS_WEB_DATABASE,
|
|
44
|
+
WebTemplateType.CHARITABLE_CONTRIBUTIONS_WEB_DATABASE,
|
|
45
|
+
WebTemplateType.CONTACTS_WEB_DATABASE,
|
|
46
|
+
WebTemplateType.ISSUES_WEB_DATABASE,
|
|
47
|
+
WebTemplateType.PROJECTS_WEB_DATABASE,
|
|
48
|
+
WebTemplateType.DOCUMENT_CENTER,
|
|
49
|
+
WebTemplateType.EXPRESS_TEAM_SITE,
|
|
50
|
+
WebTemplateType.EXPRESS_HOSTED_SITE,
|
|
51
|
+
WebTemplateType.MEETING_BASIC,
|
|
52
|
+
WebTemplateType.MEETING_BLANK,
|
|
53
|
+
WebTemplateType.MEETING_DECISION,
|
|
54
|
+
WebTemplateType.MEETING_SOCIAL,
|
|
55
|
+
WebTemplateType.MEETING_MULTIPAGE,
|
|
56
|
+
WebTemplateType.POWERPOINT_BROADCAST,
|
|
57
|
+
WebTemplateType.BUSINESS_INTELLIGENCE_CENTER,
|
|
58
|
+
WebTemplateType.VISIO_PROCESS_REPOSITORY,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass
|
|
64
|
+
class UnsupportedSiteTemplatesRecord:
|
|
65
|
+
"""One row of the SMAT ``UnsupportedSiteTemplates-detail`` report."""
|
|
66
|
+
|
|
67
|
+
URL: str | None = None
|
|
68
|
+
Template: str | None = None
|
|
69
|
+
ScanID: str | None = None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class UnsupportedSiteTemplatesScanner(BaseScanner[UnsupportedSiteTemplatesRecord]):
|
|
73
|
+
"""SITE-container scan: reports legacy site templates with no Microsoft 365 equivalent."""
|
|
74
|
+
|
|
75
|
+
category = "site"
|
|
76
|
+
scan_name = "UnsupportedSiteTemplates"
|
|
77
|
+
record_type = UnsupportedSiteTemplatesRecord
|
|
78
|
+
|
|
79
|
+
def run(self, target: ScanTarget[SiteScanSummary], report: AssessmentReport) -> None:
|
|
80
|
+
summary = target.entity
|
|
81
|
+
template = summary.web_template
|
|
82
|
+
if not template or template.split("#", 1)[0].upper() not in _UNSUPPORTED_TEMPLATES:
|
|
83
|
+
return
|
|
84
|
+
self.records.append(
|
|
85
|
+
UnsupportedSiteTemplatesRecord(
|
|
86
|
+
URL=summary.site_url,
|
|
87
|
+
Template=template,
|
|
88
|
+
ScanID=report.scan_id or None,
|
|
89
|
+
)
|
|
90
|
+
)
|